Passed
Push — 1.11.x ( 61d9de...31bbc5 )
by Julito
09:19 queued 10s
created

learnpath   F

Complexity

Total Complexity 1921

Size/Duplication

Total Lines 14153
Duplicated Lines 0 %

Importance

Changes 8
Bugs 0 Features 1
Metric Value
eloc 7833
dl 0
loc 14153
rs 0.8
c 8
b 0
f 1
wmc 1921

226 Methods

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

How to fix   Complexity   

Complex Class

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

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

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

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\Repository\CourseRepository;
6
use Chamilo\CoreBundle\Entity\Repository\ItemPropertyRepository;
7
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
8
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
9
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
10
use Chamilo\CourseBundle\Entity\CDocument;
11
use Chamilo\CourseBundle\Entity\CItemProperty;
12
use Chamilo\CourseBundle\Entity\CLp;
13
use Chamilo\CourseBundle\Entity\CLpCategory;
14
use Chamilo\CourseBundle\Entity\CLpItem;
15
use Chamilo\CourseBundle\Entity\CLpItemView;
16
use Chamilo\CourseBundle\Entity\CTool;
17
use Chamilo\UserBundle\Entity\User;
18
use ChamiloSession as Session;
19
use Gedmo\Sortable\Entity\Repository\SortableRepository;
20
use Symfony\Component\Filesystem\Filesystem;
21
use Symfony\Component\Finder\Finder;
22
23
/**
24
 * Class learnpath
25
 * This class defines the parent attributes and methods for Chamilo learnpaths
26
 * and SCORM learnpaths. It is used by the scorm class.
27
 *
28
 * @todo decouple class
29
 *
30
 * @author  Yannick Warnier <[email protected]>
31
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
32
 */
33
class learnpath
34
{
35
    public const MAX_LP_ITEM_TITLE_LENGTH = 32;
36
    public const STATUS_CSS_CLASS_NAME = [
37
        'not attempted' => 'scorm_not_attempted',
38
        'incomplete' => 'scorm_not_attempted',
39
        'failed' => 'scorm_failed',
40
        'completed' => 'scorm_completed',
41
        'passed' => 'scorm_completed',
42
        'succeeded' => 'scorm_completed',
43
        'browsed' => 'scorm_completed',
44
    ];
45
46
    public $attempt = 0; // The number for the current ID view.
47
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
48
    public $current; // Id of the current item the user is viewing.
49
    public $current_score; // The score of the current item.
50
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
51
    public $current_time_stop; // The time the user closed this resource.
52
    public $default_status = 'not attempted';
53
    public $encoding = 'UTF-8';
54
    public $error = '';
55
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
56
    public $index; // The index of the active learnpath_item in $ordered_items array.
57
    /** @var learnpathItem[] */
58
    public $items = [];
59
    public $last; // item_id of last item viewed in the learning path.
60
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
61
    public $license; // Which license this course has been given - not used yet on 20060522.
62
    public $lp_id; // DB iid for this learnpath.
63
    public $lp_view_id; // DB ID for lp_view
64
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
65
    public $message = '';
66
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
67
    public $name; // Learnpath name (they generally have one).
68
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
69
    public $path = ''; // Path inside the scorm directory (if scorm).
70
    public $theme; // The current theme of the learning path.
71
    public $preview_image; // The current image of the learning path.
72
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
73
    public $accumulateWorkTime; // The min time of learnpath
74
75
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
76
    public $prevent_reinit = 1;
77
78
    // Describes the mode of progress bar display.
79
    public $seriousgame_mode = 0;
80
    public $progress_bar_mode = '%';
81
82
    // Percentage progress as saved in the db.
83
    public $progress_db = 0;
84
    public $proximity; // Wether the content is distant or local or unknown.
85
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
86
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
87
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
88
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
89
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
90
    public $user_id; //ID of the user that is viewing/using the course
91
    public $update_queue = [];
92
    public $scorm_debug = 0;
93
    public $arrMenu = []; // Array for the menu items.
94
    public $debug = 0; // Logging level.
95
    public $lp_session_id = 0;
96
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
97
    public $prerequisite = 0;
98
    public $use_max_score = 1; // 1 or 0
99
    public $subscribeUsers = 0; // Subscribe users or not
100
    public $created_on = '';
101
    public $modified_on = '';
102
    public $publicated_on = '';
103
    public $expired_on = '';
104
    public $ref = null;
105
    public $course_int_id;
106
    public $course_info = [];
107
    public $categoryId;
108
109
    /**
110
     * Constructor.
111
     * Needs a database handler, a course code and a learnpath id from the database.
112
     * Also builds the list of items into $this->items.
113
     *
114
     * @param string $course  Course code
115
     * @param int    $lp_id   c_lp.iid
116
     * @param int    $user_id
117
     */
118
    public function __construct($course, $lp_id, $user_id)
119
    {
120
        $debug = $this->debug;
121
        $this->encoding = api_get_system_encoding();
122
        if (empty($course)) {
123
            $course = api_get_course_id();
124
        }
125
        $course_info = api_get_course_info($course);
126
        if (!empty($course_info)) {
127
            $this->cc = $course_info['code'];
128
            $this->course_info = $course_info;
129
            $course_id = $course_info['real_id'];
130
        } else {
131
            $this->error = 'Course code does not exist in database.';
132
        }
133
134
        $lp_id = (int) $lp_id;
135
        $course_id = (int) $course_id;
136
        $this->set_course_int_id($course_id);
137
        // Check learnpath ID.
138
        if (empty($lp_id) || empty($course_id)) {
139
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
140
        } else {
141
            // TODO: Make it flexible to use any course_code (still using env course code here).
142
            $lp_table = Database::get_course_table(TABLE_LP_MAIN);
143
            $sql = "SELECT * FROM $lp_table
144
                    WHERE iid = $lp_id";
145
            if ($debug) {
146
                error_log('learnpath::__construct() '.__LINE__.' - Querying lp: '.$sql, 0);
147
            }
148
            $res = Database::query($sql);
149
            if (Database::num_rows($res) > 0) {
150
                $this->lp_id = $lp_id;
151
                $row = Database::fetch_array($res);
152
                $this->type = $row['lp_type'];
153
                $this->name = stripslashes($row['name']);
154
                $this->proximity = $row['content_local'];
155
                $this->theme = $row['theme'];
156
                $this->maker = $row['content_maker'];
157
                $this->prevent_reinit = $row['prevent_reinit'];
158
                $this->seriousgame_mode = $row['seriousgame_mode'];
159
                $this->license = $row['content_license'];
160
                $this->scorm_debug = $row['debug'];
161
                $this->js_lib = $row['js_lib'];
162
                $this->path = $row['path'];
163
                $this->preview_image = $row['preview_image'];
164
                $this->author = $row['author'];
165
                $this->hide_toc_frame = $row['hide_toc_frame'];
166
                $this->lp_session_id = $row['session_id'];
167
                $this->use_max_score = $row['use_max_score'];
168
                $this->subscribeUsers = $row['subscribe_users'];
169
                $this->created_on = $row['created_on'];
170
                $this->modified_on = $row['modified_on'];
171
                $this->ref = $row['ref'];
172
                $this->categoryId = $row['category_id'];
173
                $this->accumulateScormTime = isset($row['accumulate_scorm_time']) ? $row['accumulate_scorm_time'] : 'true';
174
                $this->accumulateWorkTime = isset($row['accumulate_work_time']) ? $row['accumulate_work_time'] : 0;
175
176
                if (!empty($row['publicated_on'])) {
177
                    $this->publicated_on = $row['publicated_on'];
178
                }
179
180
                if (!empty($row['expired_on'])) {
181
                    $this->expired_on = $row['expired_on'];
182
                }
183
                if ($this->type == 2) {
184
                    if ($row['force_commit'] == 1) {
185
                        $this->force_commit = true;
186
                    }
187
                }
188
                $this->mode = $row['default_view_mod'];
189
190
                // Check user ID.
191
                if (empty($user_id)) {
192
                    $this->error = 'User ID is empty';
193
                } else {
194
                    $userInfo = api_get_user_info($user_id);
195
                    if (!empty($userInfo)) {
196
                        $this->user_id = $userInfo['user_id'];
197
                    } else {
198
                        $this->error = 'User ID does not exist in database #'.$user_id;
199
                    }
200
                }
201
202
                // End of variables checking.
203
                $session_id = api_get_session_id();
204
                //  Get the session condition for learning paths of the base + session.
205
                $session = api_get_session_condition($session_id);
206
                // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
207
                $lp_table = Database::get_course_table(TABLE_LP_VIEW);
208
209
                // Selecting by view_count descending allows to get the highest view_count first.
210
                $sql = "SELECT * FROM $lp_table
211
                        WHERE
212
                            c_id = $course_id AND
213
                            lp_id = $lp_id AND
214
                            user_id = $user_id
215
                            $session
216
                        ORDER BY view_count DESC";
217
                $res = Database::query($sql);
218
                if ($debug) {
219
                    error_log('learnpath::__construct() '.__LINE__.' - querying lp_view: '.$sql, 0);
220
                }
221
222
                if (Database::num_rows($res) > 0) {
223
                    if ($debug) {
224
                        error_log('learnpath::__construct() '.__LINE__.' - Found previous view');
225
                    }
226
                    $row = Database::fetch_array($res);
227
                    $this->attempt = $row['view_count'];
228
                    $this->lp_view_id = $row['id'];
229
                    $this->last_item_seen = $row['last_item'];
230
                    $this->progress_db = $row['progress'];
231
                    $this->lp_view_session_id = $row['session_id'];
232
                } elseif (!api_is_invitee()) {
233
                    if ($debug) {
234
                        error_log('learnpath::__construct() '.__LINE__.' - NOT Found previous view');
235
                    }
236
                    $this->attempt = 1;
237
                    $params = [
238
                        'c_id' => $course_id,
239
                        'lp_id' => $lp_id,
240
                        'user_id' => $user_id,
241
                        'view_count' => 1,
242
                        'session_id' => $session_id,
243
                        'last_item' => 0,
244
                    ];
245
                    $this->last_item_seen = 0;
246
                    $this->lp_view_session_id = $session_id;
247
                    $this->lp_view_id = Database::insert($lp_table, $params);
248
                    if (!empty($this->lp_view_id)) {
249
                        $sql = "UPDATE $lp_table SET id = iid
250
                                WHERE iid = ".$this->lp_view_id;
251
                        Database::query($sql);
252
                    }
253
                }
254
255
                // Initialise items.
256
                $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
257
                $sql = "SELECT * FROM $lp_item_table
258
                        WHERE c_id = $course_id AND lp_id = '".$this->lp_id."'
259
                        ORDER BY parent_item_id, display_order";
260
                $res = Database::query($sql);
261
262
                $lp_item_id_list = [];
263
                while ($row = Database::fetch_array($res)) {
264
                    $lp_item_id_list[] = $row['iid'];
265
                    switch ($this->type) {
266
                        case 3: //aicc
267
                            $oItem = new aiccItem('db', $row['iid'], $course_id);
268
                            if (is_object($oItem)) {
269
                                $my_item_id = $oItem->get_id();
270
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
271
                                $oItem->set_prevent_reinit($this->prevent_reinit);
272
                                // Don't use reference here as the next loop will make the pointed object change.
273
                                $this->items[$my_item_id] = $oItem;
274
                                $this->refs_list[$oItem->ref] = $my_item_id;
275
                                if ($debug) {
276
                                    error_log(
277
                                        'learnpath::__construct() - '.
278
                                        'aicc object with id '.$my_item_id.
279
                                        ' set in items[]',
280
                                        0
281
                                    );
282
                                }
283
                            }
284
                            break;
285
                        case 2:
286
                            $oItem = new scormItem('db', $row['iid'], $course_id);
287
                            if (is_object($oItem)) {
288
                                $my_item_id = $oItem->get_id();
289
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
290
                                $oItem->set_prevent_reinit($this->prevent_reinit);
291
                                // Don't use reference here as the next loop will make the pointed object change.
292
                                $this->items[$my_item_id] = $oItem;
293
                                $this->refs_list[$oItem->ref] = $my_item_id;
294
                                if ($debug) {
295
                                    error_log('object with id '.$my_item_id.' set in items[]');
296
                                }
297
                            }
298
                            break;
299
                        case 1:
300
                        default:
301
                            if ($debug) {
302
                                error_log('learnpath::__construct() '.__LINE__.' - calling learnpathItem');
303
                            }
304
                            $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
305
306
                            if ($debug) {
307
                                error_log('learnpath::__construct() '.__LINE__.' - end calling learnpathItem');
308
                            }
309
                            if (is_object($oItem)) {
310
                                $my_item_id = $oItem->get_id();
311
                                // Moved down to when we are sure the item_view exists.
312
                                //$oItem->set_lp_view($this->lp_view_id);
313
                                $oItem->set_prevent_reinit($this->prevent_reinit);
314
                                // Don't use reference here as the next loop will make the pointed object change.
315
                                $this->items[$my_item_id] = $oItem;
316
                                $this->refs_list[$my_item_id] = $my_item_id;
317
                                if ($debug) {
318
                                    error_log(
319
                                        'learnpath::__construct() '.__LINE__.
320
                                        ' - object with id '.$my_item_id.' set in items[]'
321
                                    );
322
                                }
323
                            }
324
                            break;
325
                    }
326
327
                    // Setting the object level with variable $this->items[$i][parent]
328
                    foreach ($this->items as $itemLPObject) {
329
                        $level = self::get_level_for_item(
330
                            $this->items,
331
                            $itemLPObject->db_id
332
                        );
333
                        $itemLPObject->level = $level;
334
                    }
335
336
                    // Setting the view in the item object.
337
                    if (is_object($this->items[$row['iid']])) {
338
                        $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
339
                        if ($this->items[$row['iid']]->get_type() == TOOL_HOTPOTATOES) {
340
                            $this->items[$row['iid']]->current_start_time = 0;
341
                            $this->items[$row['iid']]->current_stop_time = 0;
342
                        }
343
                    }
344
                }
345
346
                if (!empty($lp_item_id_list)) {
347
                    $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
348
                    if (!empty($lp_item_id_list_to_string)) {
349
                        // Get last viewing vars.
350
                        $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
351
                        // This query should only return one or zero result.
352
                        $sql = "SELECT lp_item_id, status
353
                                FROM $itemViewTable
354
                                WHERE
355
                                    c_id = $course_id AND
356
                                    lp_view_id = ".$this->get_view_id()." AND
357
                                    lp_item_id IN ('".$lp_item_id_list_to_string."')
358
                                ORDER BY view_count DESC ";
359
                        $status_list = [];
360
                        $res = Database::query($sql);
361
                        while ($row = Database:: fetch_array($res)) {
362
                            $status_list[$row['lp_item_id']] = $row['status'];
363
                        }
364
365
                        foreach ($lp_item_id_list as $item_id) {
366
                            if (isset($status_list[$item_id])) {
367
                                $status = $status_list[$item_id];
368
                                if (is_object($this->items[$item_id])) {
369
                                    $this->items[$item_id]->set_status($status);
370
                                    if (empty($status)) {
371
                                        $this->items[$item_id]->set_status(
372
                                            $this->default_status
373
                                        );
374
                                    }
375
                                }
376
                            } else {
377
                                if (!api_is_invitee()) {
378
                                    if (is_object($this->items[$item_id])) {
379
                                        $this->items[$item_id]->set_status(
380
                                            $this->default_status
381
                                        );
382
                                    }
383
384
                                    if (!empty($this->lp_view_id)) {
385
                                        // Add that row to the lp_item_view table so that
386
                                        // we have something to show in the stats page.
387
                                        $params = [
388
                                            'c_id' => $course_id,
389
                                            'lp_item_id' => $item_id,
390
                                            'lp_view_id' => $this->lp_view_id,
391
                                            'view_count' => 1,
392
                                            'status' => 'not attempted',
393
                                            'start_time' => time(),
394
                                            'total_time' => 0,
395
                                            'score' => 0,
396
                                        ];
397
                                        $insertId = Database::insert($itemViewTable, $params);
398
399
                                        if ($insertId) {
400
                                            $sql = "UPDATE $itemViewTable SET id = iid
401
                                                    WHERE iid = $insertId";
402
                                            Database::query($sql);
403
                                        }
404
405
                                        $this->items[$item_id]->set_lp_view(
406
                                            $this->lp_view_id,
407
                                            $course_id
408
                                        );
409
                                    }
410
                                }
411
                            }
412
                        }
413
                    }
414
                }
415
416
                $this->ordered_items = self::get_flat_ordered_items_list(
417
                    $this->get_id(),
418
                    0,
419
                    $course_id
420
                );
421
                $this->max_ordered_items = 0;
422
                foreach ($this->ordered_items as $index => $dummy) {
423
                    if ($index > $this->max_ordered_items && !empty($dummy)) {
424
                        $this->max_ordered_items = $index;
425
                    }
426
                }
427
                // TODO: Define the current item better.
428
                $this->first();
429
                if ($debug) {
430
                    error_log('lp_view_session_id '.$this->lp_view_session_id);
431
                    error_log('End of learnpath constructor for learnpath '.$this->get_id());
432
                }
433
            } else {
434
                $this->error = 'Learnpath ID does not exist in database ('.$sql.')';
435
            }
436
        }
437
    }
438
439
    /**
440
     * @return string
441
     */
442
    public function getCourseCode()
443
    {
444
        return $this->cc;
445
    }
446
447
    /**
448
     * @return int
449
     */
450
    public function get_course_int_id()
451
    {
452
        return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
453
    }
454
455
    /**
456
     * @param $course_id
457
     *
458
     * @return int
459
     */
460
    public function set_course_int_id($course_id)
461
    {
462
        return $this->course_int_id = (int) $course_id;
463
    }
464
465
    /**
466
     * Function rewritten based on old_add_item() from Yannick Warnier.
467
     * Due the fact that users can decide where the item should come, I had to overlook this function and
468
     * I found it better to rewrite it. Old function is still available.
469
     * Added also the possibility to add a description.
470
     *
471
     * @param int    $parent
472
     * @param int    $previous
473
     * @param string $type
474
     * @param int    $id               resource ID (ref)
475
     * @param string $title
476
     * @param string $description
477
     * @param int    $prerequisites
478
     * @param int    $max_time_allowed
479
     * @param int    $userId
480
     *
481
     * @return int
482
     */
483
    public function add_item(
484
        $parent,
485
        $previous,
486
        $type = 'dir',
487
        $id,
488
        $title,
489
        $description,
490
        $prerequisites = 0,
491
        $max_time_allowed = 0,
492
        $userId = 0
493
    ) {
494
        $course_id = $this->course_info['real_id'];
495
        if (empty($course_id)) {
496
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
497
            $this->course_info = api_get_course_info($this->cc);
498
            $course_id = $this->course_info['real_id'];
499
        }
500
        $userId = empty($userId) ? api_get_user_id() : $userId;
501
        $sessionId = api_get_session_id();
502
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
503
        $_course = $this->course_info;
504
        $parent = (int) $parent;
505
        $previous = (int) $previous;
506
        $id = (int) $id;
507
        $max_time_allowed = htmlentities($max_time_allowed);
508
        if (empty($max_time_allowed)) {
509
            $max_time_allowed = 0;
510
        }
511
        $sql = "SELECT COUNT(iid) AS num
512
                FROM $tbl_lp_item
513
                WHERE
514
                    c_id = $course_id AND
515
                    lp_id = ".$this->get_id()." AND
516
                    parent_item_id = $parent ";
517
518
        $res_count = Database::query($sql);
519
        $row = Database::fetch_array($res_count);
520
        $num = $row['num'];
521
522
        $tmp_previous = 0;
523
        $display_order = 0;
524
        $next = 0;
525
        if ($num > 0) {
526
            if (empty($previous)) {
527
                $sql = "SELECT iid, next_item_id, display_order
528
                        FROM $tbl_lp_item
529
                        WHERE
530
                            c_id = $course_id AND
531
                            lp_id = ".$this->get_id()." AND
532
                            parent_item_id = $parent AND
533
                            previous_item_id = 0 OR
534
                            previous_item_id = $parent";
535
                $result = Database::query($sql);
536
                $row = Database::fetch_array($result);
537
                if ($row) {
538
                    $next = $row['iid'];
539
                }
540
            } else {
541
                $previous = (int) $previous;
542
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
543
						FROM $tbl_lp_item
544
                        WHERE
545
                            c_id = $course_id AND
546
                            lp_id = ".$this->get_id()." AND
547
                            id = $previous";
548
                $result = Database::query($sql);
549
                $row = Database::fetch_array($result);
550
                if ($row) {
551
                    $tmp_previous = $row['iid'];
552
                    $next = $row['next_item_id'];
553
                    $display_order = $row['display_order'];
554
                }
555
            }
556
        }
557
558
        $id = (int) $id;
559
        $typeCleaned = Database::escape_string($type);
560
        $max_score = 100;
561
        if ($type === 'quiz' && $id) {
562
            $sql = 'SELECT SUM(ponderation)
563
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
564
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
565
                    ON
566
                        quiz_question.id = quiz_rel_question.question_id AND
567
                        quiz_question.c_id = quiz_rel_question.c_id
568
                    WHERE
569
                        quiz_rel_question.exercice_id = '.$id." AND
570
                        quiz_question.c_id = $course_id AND
571
                        quiz_rel_question.c_id = $course_id ";
572
            $rsQuiz = Database::query($sql);
573
            $max_score = Database::result($rsQuiz, 0, 0);
574
575
            // Disabling the exercise if we add it inside a LP
576
            $exercise = new Exercise($course_id);
577
            $exercise->read($id);
578
            $exercise->disable();
579
            $exercise->save();
580
        }
581
582
        $params = [
583
            'c_id' => $course_id,
584
            'lp_id' => $this->get_id(),
585
            'item_type' => $typeCleaned,
586
            'ref' => '',
587
            'title' => $title,
588
            'description' => $description,
589
            'path' => $id,
590
            'max_score' => $max_score,
591
            'parent_item_id' => $parent,
592
            'previous_item_id' => $previous,
593
            'next_item_id' => (int) $next,
594
            'display_order' => $display_order + 1,
595
            'prerequisite' => $prerequisites,
596
            'max_time_allowed' => $max_time_allowed,
597
            'min_score' => 0,
598
            'launch_data' => '',
599
        ];
600
601
        if ($prerequisites != 0) {
602
            $params['prerequisite'] = $prerequisites;
603
        }
604
605
        $new_item_id = Database::insert($tbl_lp_item, $params);
606
        if ($new_item_id) {
607
            $sql = "UPDATE $tbl_lp_item SET id = iid WHERE iid = $new_item_id";
608
            Database::query($sql);
609
610
            if (!empty($next)) {
611
                $sql = "UPDATE $tbl_lp_item
612
                        SET previous_item_id = $new_item_id
613
                        WHERE c_id = $course_id AND id = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
614
                Database::query($sql);
615
            }
616
617
            // Update the item that should be before the new item.
618
            if (!empty($tmp_previous)) {
619
                $sql = "UPDATE $tbl_lp_item
620
                        SET next_item_id = $new_item_id
621
                        WHERE c_id = $course_id AND id = $tmp_previous";
622
                Database::query($sql);
623
            }
624
625
            // Update all the items after the new item.
626
            $sql = "UPDATE $tbl_lp_item
627
                        SET display_order = display_order + 1
628
                    WHERE
629
                        c_id = $course_id AND
630
                        lp_id = ".$this->get_id()." AND
631
                        iid <> $new_item_id AND
632
                        parent_item_id = $parent AND
633
                        display_order > $display_order";
634
            Database::query($sql);
635
636
            // Update the item that should come after the new item.
637
            $sql = "UPDATE $tbl_lp_item
638
                    SET ref = $new_item_id
639
                    WHERE c_id = $course_id AND iid = $new_item_id";
640
            Database::query($sql);
641
642
            $sql = "UPDATE $tbl_lp_item
643
                    SET previous_item_id = ".$this->getLastInFirstLevel()."
644
                    WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
645
            Database::query($sql);
646
647
            // Upload audio.
648
            if (!empty($_FILES['mp3']['name'])) {
649
                // Create the audio folder if it does not exist yet.
650
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
651
                if (!is_dir($filepath.'audio')) {
652
                    mkdir(
653
                        $filepath.'audio',
654
                        api_get_permissions_for_new_directories()
655
                    );
656
                    $audio_id = add_document(
657
                        $_course,
658
                        '/audio',
659
                        'folder',
660
                        0,
661
                        'audio',
662
                        '',
663
                        0,
664
                        true,
665
                        null,
666
                        $sessionId,
667
                        $userId
668
                    );
669
                    api_item_property_update(
670
                        $_course,
671
                        TOOL_DOCUMENT,
672
                        $audio_id,
673
                        'FolderCreated',
674
                        $userId,
675
                        null,
676
                        null,
677
                        null,
678
                        null,
679
                        $sessionId
680
                    );
681
                    api_item_property_update(
682
                        $_course,
683
                        TOOL_DOCUMENT,
684
                        $audio_id,
685
                        'invisible',
686
                        $userId,
687
                        null,
688
                        null,
689
                        null,
690
                        null,
691
                        $sessionId
692
                    );
693
                }
694
695
                $file_path = handle_uploaded_document(
696
                    $_course,
697
                    $_FILES['mp3'],
698
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
699
                    '/audio',
700
                    $userId,
701
                    '',
702
                    '',
703
                    '',
704
                    '',
705
                    false
706
                );
707
708
                // Store the mp3 file in the lp_item table.
709
                $sql = "UPDATE $tbl_lp_item SET
710
                          audio = '".Database::escape_string($file_path)."'
711
                        WHERE iid = '".intval($new_item_id)."'";
712
                Database::query($sql);
713
            }
714
        }
715
716
        return $new_item_id;
717
    }
718
719
    /**
720
     * Static admin function allowing addition of a learnpath to a course.
721
     *
722
     * @param string $courseCode
723
     * @param string $name
724
     * @param string $description
725
     * @param string $learnpath
726
     * @param string $origin
727
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
728
     * @param string $publicated_on
729
     * @param string $expired_on
730
     * @param int    $categoryId
731
     * @param int    $userId
732
     *
733
     * @return int The new learnpath ID on success, 0 on failure
734
     */
735
    public static function add_lp(
736
        $courseCode,
737
        $name,
738
        $description = '',
739
        $learnpath = 'guess',
740
        $origin = 'zip',
741
        $zipname = '',
742
        $publicated_on = '',
743
        $expired_on = '',
744
        $categoryId = 0,
745
        $userId = 0
746
    ) {
747
        global $charset;
748
749
        if (!empty($courseCode)) {
750
            $courseInfo = api_get_course_info($courseCode);
751
            $course_id = $courseInfo['real_id'];
752
        } else {
753
            $course_id = api_get_course_int_id();
754
            $courseInfo = api_get_course_info();
755
        }
756
757
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
758
        // Check course code exists.
759
        // Check lp_name doesn't exist, otherwise append something.
760
        $i = 0;
761
        $categoryId = (int) $categoryId;
762
        // Session id.
763
        $session_id = api_get_session_id();
764
        $userId = empty($userId) ? api_get_user_id() : $userId;
765
766
        if (empty($publicated_on)) {
767
            $publicated_on = null;
768
        } else {
769
            $publicated_on = Database::escape_string(api_get_utc_datetime($publicated_on));
770
        }
771
772
        if (empty($expired_on)) {
773
            $expired_on = null;
774
        } else {
775
            $expired_on = Database::escape_string(api_get_utc_datetime($expired_on));
776
        }
777
778
        $check_name = "SELECT * FROM $tbl_lp
779
                       WHERE c_id = $course_id AND name = '".Database::escape_string($name)."'";
780
        $res_name = Database::query($check_name);
781
782
        while (Database::num_rows($res_name)) {
783
            // There is already one such name, update the current one a bit.
784
            $i++;
785
            $name = $name.' - '.$i;
786
            $check_name = "SELECT * FROM $tbl_lp
787
                           WHERE c_id = $course_id AND name = '".Database::escape_string($name)."' ";
788
            $res_name = Database::query($check_name);
789
        }
790
        // New name does not exist yet; keep it.
791
        // Escape description.
792
        // Kevin: added htmlentities().
793
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
794
        $type = 1;
795
        switch ($learnpath) {
796
            case 'guess':
797
                break;
798
            case 'dokeos':
799
            case 'chamilo':
800
                $type = 1;
801
                break;
802
            case 'aicc':
803
                break;
804
        }
805
806
        switch ($origin) {
807
            case 'zip':
808
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
809
                break;
810
            case 'manual':
811
            default:
812
                $get_max = "SELECT MAX(display_order)
813
                            FROM $tbl_lp WHERE c_id = $course_id";
814
                $res_max = Database::query($get_max);
815
                if (Database::num_rows($res_max) < 1) {
816
                    $dsp = 1;
817
                } else {
818
                    $row = Database::fetch_array($res_max);
819
                    $dsp = $row[0] + 1;
820
                }
821
822
                $params = [
823
                    'c_id' => $course_id,
824
                    'lp_type' => $type,
825
                    'name' => $name,
826
                    'description' => $description,
827
                    'path' => '',
828
                    'default_view_mod' => 'embedded',
829
                    'default_encoding' => 'UTF-8',
830
                    'display_order' => $dsp,
831
                    'content_maker' => 'Chamilo',
832
                    'content_local' => 'local',
833
                    'js_lib' => '',
834
                    'session_id' => $session_id,
835
                    'created_on' => api_get_utc_datetime(),
836
                    'modified_on' => api_get_utc_datetime(),
837
                    'publicated_on' => $publicated_on,
838
                    'expired_on' => $expired_on,
839
                    'category_id' => $categoryId,
840
                    'force_commit' => 0,
841
                    'content_license' => '',
842
                    'debug' => 0,
843
                    'theme' => '',
844
                    'preview_image' => '',
845
                    'author' => '',
846
                    'prerequisite' => 0,
847
                    'hide_toc_frame' => 0,
848
                    'seriousgame_mode' => 0,
849
                    'autolaunch' => 0,
850
                    'max_attempts' => 0,
851
                    'subscribe_users' => 0,
852
                    'accumulate_scorm_time' => 1,
853
                ];
854
                $id = Database::insert($tbl_lp, $params);
855
856
                if ($id > 0) {
857
                    $sql = "UPDATE $tbl_lp SET id = iid WHERE iid = $id";
858
                    Database::query($sql);
859
860
                    // Insert into item_property.
861
                    api_item_property_update(
862
                        $courseInfo,
863
                        TOOL_LEARNPATH,
864
                        $id,
865
                        'LearnpathAdded',
866
                        $userId
867
                    );
868
                    api_set_default_visibility(
869
                        $id,
870
                        TOOL_LEARNPATH,
871
                        0,
872
                        $courseInfo,
873
                        $session_id,
874
                        $userId
875
                    );
876
877
                    return $id;
878
                }
879
                break;
880
        }
881
    }
882
883
    /**
884
     * Auto completes the parents of an item in case it's been completed or passed.
885
     *
886
     * @param int $item Optional ID of the item from which to look for parents
887
     */
888
    public function autocomplete_parents($item)
889
    {
890
        $debug = $this->debug;
891
892
        if (empty($item)) {
893
            $item = $this->current;
894
        }
895
896
        $currentItem = $this->getItem($item);
897
        if ($currentItem) {
898
            $parent_id = $currentItem->get_parent();
899
            $parent = $this->getItem($parent_id);
900
            if ($parent) {
901
                // if $item points to an object and there is a parent.
902
                if ($debug) {
903
                    error_log(
904
                        'Autocompleting parent of item '.$item.' '.
905
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
906
                        0
907
                    );
908
                }
909
910
                // New experiment including failed and browsed in completed status.
911
                //$current_status = $currentItem->get_status();
912
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
913
                // Fixes chapter auto complete
914
                if (true) {
915
                    // If the current item is completed or passes or succeeded.
916
                    $updateParentStatus = true;
917
                    if ($debug) {
918
                        error_log('Status of current item is alright');
919
                    }
920
921
                    foreach ($parent->get_children() as $childItemId) {
922
                        $childItem = $this->getItem($childItemId);
923
924
                        // If children was not set try to get the info
925
                        if (empty($childItem->db_item_view_id)) {
926
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
927
                        }
928
929
                        // Check all his brothers (parent's children) for completion status.
930
                        if ($childItemId != $item) {
931
                            if ($debug) {
932
                                error_log(
933
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
934
                                    0
935
                                );
936
                            }
937
                            // Trying completing parents of failed and browsed items as well.
938
                            if ($childItem->status_is(
939
                                [
940
                                    'completed',
941
                                    'passed',
942
                                    'succeeded',
943
                                    'browsed',
944
                                    'failed',
945
                                ]
946
                            )
947
                            ) {
948
                                // Keep completion status to true.
949
                                continue;
950
                            } else {
951
                                if ($debug > 2) {
952
                                    error_log(
953
                                        'Found one incomplete child of parent #'.$parent_id.': child #'.$childItemId.' "'.$childItem->get_title().'", is '.$childItem->get_status().' db_item_view_id:#'.$childItem->db_item_view_id,
954
                                        0
955
                                    );
956
                                }
957
                                $updateParentStatus = false;
958
                                break;
959
                            }
960
                        }
961
                    }
962
963
                    if ($updateParentStatus) {
964
                        // If all the children were completed:
965
                        $parent->set_status('completed');
966
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
967
                        // Force the status to "completed"
968
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
969
                        $this->update_queue[$parent->get_id()] = 'completed';
970
                        if ($debug) {
971
                            error_log(
972
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
973
                                print_r($this->update_queue, 1),
974
                                0
975
                            );
976
                        }
977
                        // Recursive call.
978
                        $this->autocomplete_parents($parent->get_id());
979
                    }
980
                }
981
            } else {
982
                if ($debug) {
983
                    error_log("Parent #$parent_id does not exists");
984
                }
985
            }
986
        } else {
987
            if ($debug) {
988
                error_log("#$item is an item that doesn't have parents");
989
            }
990
        }
991
    }
992
993
    /**
994
     * Closes the current resource.
995
     *
996
     * Stops the timer
997
     * Saves into the database if required
998
     * Clears the current resource data from this object
999
     *
1000
     * @return bool True on success, false on failure
1001
     */
1002
    public function close()
1003
    {
1004
        if (empty($this->lp_id)) {
1005
            $this->error = 'Trying to close this learnpath but no ID is set';
1006
1007
            return false;
1008
        }
1009
        $this->current_time_stop = time();
1010
        $this->ordered_items = [];
1011
        $this->index = 0;
1012
        unset($this->lp_id);
1013
        //unset other stuff
1014
        return true;
1015
    }
1016
1017
    /**
1018
     * Static admin function allowing removal of a learnpath.
1019
     *
1020
     * @param array  $courseInfo
1021
     * @param int    $id         Learnpath ID
1022
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
1023
     *
1024
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
1025
     */
1026
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
1027
    {
1028
        $course_id = api_get_course_int_id();
1029
        if (!empty($courseInfo)) {
1030
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
1031
        }
1032
1033
        // TODO: Implement a way of getting this to work when the current object is not set.
1034
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
1035
        // If an ID is specifically given and the current LP is not the same, prevent delete.
1036
        if (!empty($id) && ($id != $this->lp_id)) {
1037
            return false;
1038
        }
1039
1040
        $lp = Database::get_course_table(TABLE_LP_MAIN);
1041
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1042
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
1043
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1044
1045
        // Delete lp item id.
1046
        foreach ($this->items as $lpItemId => $dummy) {
1047
            $sql = "DELETE FROM $lp_item_view
1048
                    WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
1049
            Database::query($sql);
1050
        }
1051
1052
        // Proposed by Christophe (nickname: clefevre)
1053
        $sql = "DELETE FROM $lp_item
1054
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1055
        Database::query($sql);
1056
1057
        $sql = "DELETE FROM $lp_view
1058
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1059
        Database::query($sql);
1060
1061
        self::toggle_publish($this->lp_id, 'i');
1062
1063
        if ($this->type == 2 || $this->type == 3) {
1064
            // This is a scorm learning path, delete the files as well.
1065
            $sql = "SELECT path FROM $lp
1066
                    WHERE iid = ".$this->lp_id;
1067
            $res = Database::query($sql);
1068
            if (Database::num_rows($res) > 0) {
1069
                $row = Database::fetch_array($res);
1070
                $path = $row['path'];
1071
                $sql = "SELECT id FROM $lp
1072
                        WHERE
1073
                            c_id = $course_id AND
1074
                            path = '$path' AND
1075
                            iid != ".$this->lp_id;
1076
                $res = Database::query($sql);
1077
                if (Database::num_rows($res) > 0) {
1078
                    // Another learning path uses this directory, so don't delete it.
1079
                    if ($this->debug > 2) {
1080
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1081
                    }
1082
                } else {
1083
                    // No other LP uses that directory, delete it.
1084
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1085
                    // The absolute system path for this course.
1086
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
1087
                    if ($delete == 'remove' && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1088
                        if ($this->debug > 2) {
1089
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1090
                        }
1091
                        // Proposed by Christophe (clefevre).
1092
                        if (strcmp(substr($path, -2), "/.") == 0) {
1093
                            $path = substr($path, 0, -1); // Remove "." at the end.
1094
                        }
1095
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1096
                        rmdirr($course_scorm_dir.$path);
1097
                    }
1098
                }
1099
            }
1100
        }
1101
1102
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1103
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1104
        // Delete tools
1105
        $sql = "DELETE FROM $tbl_tool
1106
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1107
        Database::query($sql);
1108
1109
        if (api_get_configuration_value('allow_lp_subscription_to_usergroups')) {
1110
            $table = Database::get_course_table(TABLE_LP_REL_USERGROUP);
1111
            $sql = "DELETE FROM $table
1112
                    WHERE
1113
                        lp_id = {$this->lp_id} AND
1114
                        c_id = $course_id ";
1115
            Database::query($sql);
1116
        }
1117
1118
        $sql = "DELETE FROM $lp
1119
                WHERE iid = ".$this->lp_id;
1120
        Database::query($sql);
1121
        // Updates the display order of all lps.
1122
        $this->update_display_order();
1123
1124
        api_item_property_update(
1125
            api_get_course_info(),
1126
            TOOL_LEARNPATH,
1127
            $this->lp_id,
1128
            'delete',
1129
            api_get_user_id()
1130
        );
1131
1132
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1133
            api_get_course_id(),
1134
            4,
1135
            $id,
1136
            api_get_session_id()
1137
        );
1138
1139
        if ($link_info !== false) {
1140
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1141
        }
1142
1143
        if (api_get_setting('search_enabled') == 'true') {
1144
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1145
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1146
        }
1147
    }
1148
1149
    /**
1150
     * Removes all the children of one item - dangerous!
1151
     *
1152
     * @param int $id Element ID of which children have to be removed
1153
     *
1154
     * @return int Total number of children removed
1155
     */
1156
    public function delete_children_items($id)
1157
    {
1158
        $course_id = $this->course_info['real_id'];
1159
1160
        $num = 0;
1161
        $id = (int) $id;
1162
        if (empty($id) || empty($course_id)) {
1163
            return false;
1164
        }
1165
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1166
        $sql = "SELECT * FROM $lp_item
1167
                WHERE c_id = $course_id AND parent_item_id = $id";
1168
        $res = Database::query($sql);
1169
        while ($row = Database::fetch_array($res)) {
1170
            $num += $this->delete_children_items($row['iid']);
1171
            $sql = "DELETE FROM $lp_item
1172
                    WHERE c_id = $course_id AND iid = ".$row['iid'];
1173
            Database::query($sql);
1174
            $num++;
1175
        }
1176
1177
        return $num;
1178
    }
1179
1180
    /**
1181
     * Removes an item from the current learnpath.
1182
     *
1183
     * @param int $id Elem ID (0 if first)
1184
     *
1185
     * @return int Number of elements moved
1186
     *
1187
     * @todo implement resource removal
1188
     */
1189
    public function delete_item($id)
1190
    {
1191
        $course_id = api_get_course_int_id();
1192
        $id = (int) $id;
1193
        // TODO: Implement the resource removal.
1194
        if (empty($id) || empty($course_id)) {
1195
            return false;
1196
        }
1197
        // First select item to get previous, next, and display order.
1198
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1199
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1200
        $res_sel = Database::query($sql_sel);
1201
        if (Database::num_rows($res_sel) < 1) {
1202
            return false;
1203
        }
1204
        $row = Database::fetch_array($res_sel);
1205
        $previous = $row['previous_item_id'];
1206
        $next = $row['next_item_id'];
1207
        $display = $row['display_order'];
1208
        $parent = $row['parent_item_id'];
1209
        $lp = $row['lp_id'];
1210
        // Delete children items.
1211
        $this->delete_children_items($id);
1212
        // Now delete the item.
1213
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1214
        Database::query($sql_del);
1215
        // Now update surrounding items.
1216
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1217
                    WHERE iid = $previous";
1218
        Database::query($sql_upd);
1219
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1220
                    WHERE iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
1221
        Database::query($sql_upd);
1222
        // Now update all following items with new display order.
1223
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1224
                    WHERE
1225
                        c_id = $course_id AND
1226
                        lp_id = $lp AND
1227
                        parent_item_id = $parent AND
1228
                        display_order > $display";
1229
        Database::query($sql_all);
1230
1231
        //Removing prerequisites since the item will not longer exist
1232
        $sql_all = "UPDATE $lp_item SET prerequisite = ''
1233
                    WHERE c_id = $course_id AND prerequisite = $id";
1234
        Database::query($sql_all);
1235
1236
        $sql = "UPDATE $lp_item
1237
                SET previous_item_id = ".$this->getLastInFirstLevel()."
1238
                WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
1239
        Database::query($sql);
1240
1241
        // Remove from search engine if enabled.
1242
        if (api_get_setting('search_enabled') === 'true') {
1243
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1244
            $sql = 'SELECT * FROM %s
1245
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1246
                    LIMIT 1';
1247
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1248
            $res = Database::query($sql);
1249
            if (Database::num_rows($res) > 0) {
1250
                $row2 = Database::fetch_array($res);
1251
                $di = new ChamiloIndexer();
1252
                $di->remove_document($row2['search_did']);
1253
            }
1254
            $sql = 'DELETE FROM %s
1255
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1256
                    LIMIT 1';
1257
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1258
            Database::query($sql);
1259
        }
1260
    }
1261
1262
    /**
1263
     * Updates an item's content in place.
1264
     *
1265
     * @param int    $id               Element ID
1266
     * @param int    $parent           Parent item ID
1267
     * @param int    $previous         Previous item ID
1268
     * @param string $title            Item title
1269
     * @param string $description      Item description
1270
     * @param string $prerequisites    Prerequisites (optional)
1271
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1272
     * @param int    $max_time_allowed
1273
     * @param string $url
1274
     *
1275
     * @return bool True on success, false on error
1276
     */
1277
    public function edit_item(
1278
        $id,
1279
        $parent,
1280
        $previous,
1281
        $title,
1282
        $description,
1283
        $prerequisites = '0',
1284
        $audio = [],
1285
        $max_time_allowed = 0,
1286
        $url = ''
1287
    ) {
1288
        $course_id = api_get_course_int_id();
1289
        $_course = api_get_course_info();
1290
        $id = (int) $id;
1291
1292
        if (empty($max_time_allowed)) {
1293
            $max_time_allowed = 0;
1294
        }
1295
1296
        if (empty($id) || empty($_course)) {
1297
            return false;
1298
        }
1299
1300
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1301
        $sql = "SELECT * FROM $tbl_lp_item
1302
                WHERE iid = $id";
1303
        $res_select = Database::query($sql);
1304
        $row_select = Database::fetch_array($res_select);
1305
        $audio_update_sql = '';
1306
        if (is_array($audio) && !empty($audio['tmp_name']) && $audio['error'] === 0) {
1307
            // Create the audio folder if it does not exist yet.
1308
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1309
            if (!is_dir($filepath.'audio')) {
1310
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1311
                $audio_id = add_document(
1312
                    $_course,
1313
                    '/audio',
1314
                    'folder',
1315
                    0,
1316
                    'audio'
1317
                );
1318
                api_item_property_update(
1319
                    $_course,
1320
                    TOOL_DOCUMENT,
1321
                    $audio_id,
1322
                    'FolderCreated',
1323
                    api_get_user_id(),
1324
                    null,
1325
                    null,
1326
                    null,
1327
                    null,
1328
                    api_get_session_id()
1329
                );
1330
                api_item_property_update(
1331
                    $_course,
1332
                    TOOL_DOCUMENT,
1333
                    $audio_id,
1334
                    'invisible',
1335
                    api_get_user_id(),
1336
                    null,
1337
                    null,
1338
                    null,
1339
                    null,
1340
                    api_get_session_id()
1341
                );
1342
            }
1343
1344
            // Upload file in documents.
1345
            $pi = pathinfo($audio['name']);
1346
            if ($pi['extension'] === 'mp3') {
1347
                $c_det = api_get_course_info($this->cc);
1348
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1349
                $path = handle_uploaded_document(
1350
                    $c_det,
1351
                    $audio,
1352
                    $bp,
1353
                    '/audio',
1354
                    api_get_user_id(),
1355
                    0,
1356
                    null,
1357
                    0,
1358
                    'rename',
1359
                    false,
1360
                    0
1361
                );
1362
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1363
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1364
            }
1365
        }
1366
1367
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1368
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1369
1370
        // TODO: htmlspecialchars to be checked for encoding related problems.
1371
        if ($same_parent && $same_previous) {
1372
            // Only update title and description.
1373
            $sql = "UPDATE $tbl_lp_item
1374
                    SET title = '".Database::escape_string($title)."',
1375
                        prerequisite = '".$prerequisites."',
1376
                        description = '".Database::escape_string($description)."'
1377
                        ".$audio_update_sql.",
1378
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1379
                    WHERE iid = $id";
1380
            Database::query($sql);
1381
        } else {
1382
            $old_parent = $row_select['parent_item_id'];
1383
            $old_previous = $row_select['previous_item_id'];
1384
            $old_next = $row_select['next_item_id'];
1385
            $old_order = $row_select['display_order'];
1386
            $old_prerequisite = $row_select['prerequisite'];
1387
            $old_max_time_allowed = $row_select['max_time_allowed'];
1388
1389
            /* BEGIN -- virtually remove the current item id */
1390
            /* for the next and previous item it is like the current item doesn't exist anymore */
1391
            if ($old_previous != 0) {
1392
                // Next
1393
                $sql = "UPDATE $tbl_lp_item
1394
                        SET next_item_id = $old_next
1395
                        WHERE iid = $old_previous";
1396
                Database::query($sql);
1397
            }
1398
1399
            if (!empty($old_next)) {
1400
                // Previous
1401
                $sql = "UPDATE $tbl_lp_item
1402
                        SET previous_item_id = $old_previous
1403
                        WHERE iid = $old_next";
1404
                Database::query($sql);
1405
            }
1406
1407
            // display_order - 1 for every item with a display_order
1408
            // bigger then the display_order of the current item.
1409
            $sql = "UPDATE $tbl_lp_item
1410
                    SET display_order = display_order - 1
1411
                    WHERE
1412
                        c_id = $course_id AND
1413
                        display_order > $old_order AND
1414
                        lp_id = ".$this->lp_id." AND
1415
                        parent_item_id = $old_parent";
1416
            Database::query($sql);
1417
            /* END -- virtually remove the current item id */
1418
1419
            /* BEGIN -- update the current item id to his new location */
1420
            if ($previous == 0) {
1421
                // Select the data of the item that should come after the current item.
1422
                $sql = "SELECT id, display_order
1423
                        FROM $tbl_lp_item
1424
                        WHERE
1425
                            c_id = $course_id AND
1426
                            lp_id = ".$this->lp_id." AND
1427
                            parent_item_id = $parent AND
1428
                            previous_item_id = $previous";
1429
                $res_select_old = Database::query($sql);
1430
                $row_select_old = Database::fetch_array($res_select_old);
1431
1432
                // If the new parent didn't have children before.
1433
                if (Database::num_rows($res_select_old) == 0) {
1434
                    $new_next = 0;
1435
                    $new_order = 1;
1436
                } else {
1437
                    $new_next = $row_select_old['id'];
1438
                    $new_order = $row_select_old['display_order'];
1439
                }
1440
            } else {
1441
                // Select the data of the item that should come before the current item.
1442
                $sql = "SELECT next_item_id, display_order
1443
                        FROM $tbl_lp_item
1444
                        WHERE iid = $previous";
1445
                $res_select_old = Database::query($sql);
1446
                $row_select_old = Database::fetch_array($res_select_old);
1447
                $new_next = $row_select_old['next_item_id'];
1448
                $new_order = $row_select_old['display_order'] + 1;
1449
            }
1450
1451
            // TODO: htmlspecialchars to be checked for encoding related problems.
1452
            // Update the current item with the new data.
1453
            $sql = "UPDATE $tbl_lp_item
1454
                    SET
1455
                        title = '".Database::escape_string($title)."',
1456
                        description = '".Database::escape_string($description)."',
1457
                        parent_item_id = $parent,
1458
                        previous_item_id = $previous,
1459
                        next_item_id = $new_next,
1460
                        display_order = $new_order
1461
                        $audio_update_sql
1462
                    WHERE iid = $id";
1463
            Database::query($sql);
1464
1465
            if ($previous != 0) {
1466
                // Update the previous item's next_item_id.
1467
                $sql = "UPDATE $tbl_lp_item
1468
                        SET next_item_id = $id
1469
                        WHERE iid = $previous";
1470
                Database::query($sql);
1471
            }
1472
1473
            if (!empty($new_next)) {
1474
                // Update the next item's previous_item_id.
1475
                $sql = "UPDATE $tbl_lp_item
1476
                        SET previous_item_id = $id
1477
                        WHERE iid = $new_next";
1478
                Database::query($sql);
1479
            }
1480
1481
            if ($old_prerequisite != $prerequisites) {
1482
                $sql = "UPDATE $tbl_lp_item
1483
                        SET prerequisite = '$prerequisites'
1484
                        WHERE iid = $id";
1485
                Database::query($sql);
1486
            }
1487
1488
            if ($old_max_time_allowed != $max_time_allowed) {
1489
                // update max time allowed
1490
                $sql = "UPDATE $tbl_lp_item
1491
                        SET max_time_allowed = $max_time_allowed
1492
                        WHERE iid = $id";
1493
                Database::query($sql);
1494
            }
1495
1496
            // Update all the items with the same or a bigger display_order than the current item.
1497
            $sql = "UPDATE $tbl_lp_item
1498
                    SET display_order = display_order + 1
1499
                    WHERE
1500
                       c_id = $course_id AND
1501
                       lp_id = ".$this->get_id()." AND
1502
                       iid <> $id AND
1503
                       parent_item_id = $parent AND
1504
                       display_order >= $new_order";
1505
            Database::query($sql);
1506
        }
1507
1508
        if ($row_select['item_type'] == 'link') {
1509
            $link = new Link();
1510
            $linkId = $row_select['path'];
1511
            $link->updateLink($linkId, $url);
1512
        }
1513
    }
1514
1515
    /**
1516
     * Updates an item's prereq in place.
1517
     *
1518
     * @param int    $id              Element ID
1519
     * @param string $prerequisite_id Prerequisite Element ID
1520
     * @param int    $minScore        Prerequisite min score
1521
     * @param int    $maxScore        Prerequisite max score
1522
     *
1523
     * @return bool True on success, false on error
1524
     */
1525
    public function edit_item_prereq(
1526
        $id,
1527
        $prerequisite_id,
1528
        $minScore = 0,
1529
        $maxScore = 100
1530
    ) {
1531
        $id = (int) $id;
1532
1533
        if (empty($id)) {
1534
            return false;
1535
        }
1536
1537
        $prerequisite_id = (int) $prerequisite_id;
1538
1539
        if (empty($minScore) || $minScore < 0) {
1540
            $minScore = 0;
1541
        }
1542
1543
        if (empty($maxScore) || $maxScore < 0) {
1544
            $maxScore = 100;
1545
        }
1546
1547
        $minScore = (float) $minScore;
1548
        $maxScore = (float) $maxScore;
1549
1550
        if (empty($prerequisite_id)) {
1551
            $prerequisite_id = 'NULL';
1552
            $minScore = 0;
1553
            $maxScore = 100;
1554
        }
1555
1556
        $table = Database::get_course_table(TABLE_LP_ITEM);
1557
        $sql = " UPDATE $table
1558
                 SET
1559
                    prerequisite = $prerequisite_id ,
1560
                    prerequisite_min_score = $minScore ,
1561
                    prerequisite_max_score = $maxScore
1562
                 WHERE iid = $id";
1563
        Database::query($sql);
1564
1565
        return true;
1566
    }
1567
1568
    /**
1569
     * Get the specific prefix index terms of this learning path.
1570
     *
1571
     * @param string $prefix
1572
     *
1573
     * @return array Array of terms
1574
     */
1575
    public function get_common_index_terms_by_prefix($prefix)
1576
    {
1577
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1578
        $terms = get_specific_field_values_list_by_prefix(
1579
            $prefix,
1580
            $this->cc,
1581
            TOOL_LEARNPATH,
1582
            $this->lp_id
1583
        );
1584
        $prefix_terms = [];
1585
        if (!empty($terms)) {
1586
            foreach ($terms as $term) {
1587
                $prefix_terms[] = $term['value'];
1588
            }
1589
        }
1590
1591
        return $prefix_terms;
1592
    }
1593
1594
    /**
1595
     * Gets the number of items currently completed.
1596
     *
1597
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1598
     *
1599
     * @return int The number of items currently completed
1600
     */
1601
    public function get_complete_items_count($failedStatusException = false)
1602
    {
1603
        $i = 0;
1604
        $completedStatusList = [
1605
            'completed',
1606
            'passed',
1607
            'succeeded',
1608
            'browsed',
1609
        ];
1610
1611
        if (!$failedStatusException) {
1612
            $completedStatusList[] = 'failed';
1613
        }
1614
1615
        if ($this->debug) {
1616
            error_log('START - get_complete_items_count');
1617
            error_log('Counting steps with status in: '.print_r($completedStatusList, 1));
1618
        }
1619
1620
        foreach ($this->items as $id => $dummy) {
1621
            // Trying failed and browsed considered "progressed" as well.
1622
            if ($this->items[$id]->status_is($completedStatusList) &&
1623
                $this->items[$id]->get_type() !== 'dir'
1624
            ) {
1625
                $i++;
1626
            }
1627
        }
1628
1629
        if ($this->debug) {
1630
            error_log('END - Count: '.$i);
1631
        }
1632
1633
        return $i;
1634
    }
1635
1636
    /**
1637
     * Gets the current item ID.
1638
     *
1639
     * @return int The current learnpath item id
1640
     */
1641
    public function get_current_item_id()
1642
    {
1643
        $current = 0;
1644
        if (!empty($this->current)) {
1645
            $current = (int) $this->current;
1646
        }
1647
1648
        return $current;
1649
    }
1650
1651
    /**
1652
     * Force to get the first learnpath item id.
1653
     *
1654
     * @return int The current learnpath item id
1655
     */
1656
    public function get_first_item_id()
1657
    {
1658
        $current = 0;
1659
        if (is_array($this->ordered_items)) {
1660
            $current = $this->ordered_items[0];
1661
        }
1662
1663
        return $current;
1664
    }
1665
1666
    /**
1667
     * Gets the total number of items available for viewing in this SCORM.
1668
     *
1669
     * @return int The total number of items
1670
     */
1671
    public function get_total_items_count()
1672
    {
1673
        return count($this->items);
1674
    }
1675
1676
    /**
1677
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1678
     *
1679
     * @return int The total no-chapters number of items
1680
     */
1681
    public function getTotalItemsCountWithoutDirs()
1682
    {
1683
        $total = 0;
1684
        $typeListNotToCount = self::getChapterTypes();
1685
        foreach ($this->items as $temp2) {
1686
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1687
                $total++;
1688
            }
1689
        }
1690
1691
        return $total;
1692
    }
1693
1694
    /**
1695
     *  Sets the first element URL.
1696
     */
1697
    public function first()
1698
    {
1699
        if ($this->debug > 0) {
1700
            error_log('In learnpath::first()', 0);
1701
            error_log('$this->last_item_seen '.$this->last_item_seen);
1702
        }
1703
1704
        // Test if the last_item_seen exists and is not a dir.
1705
        if (count($this->ordered_items) == 0) {
1706
            $this->index = 0;
1707
        }
1708
1709
        if (!empty($this->last_item_seen) &&
1710
            !empty($this->items[$this->last_item_seen]) &&
1711
            $this->items[$this->last_item_seen]->get_type() != 'dir'
1712
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1713
            //&& !$this->items[$this->last_item_seen]->is_done()
1714
        ) {
1715
            if ($this->debug > 2) {
1716
                error_log(
1717
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1718
                    $this->items[$this->last_item_seen]->get_type()
1719
                );
1720
            }
1721
            $index = -1;
1722
            foreach ($this->ordered_items as $myindex => $item_id) {
1723
                if ($item_id == $this->last_item_seen) {
1724
                    $index = $myindex;
1725
                    break;
1726
                }
1727
            }
1728
            if ($index == -1) {
1729
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1730
                if ($this->debug > 2) {
1731
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1732
                }
1733
1734
                return false;
1735
            } else {
1736
                $this->last = $this->last_item_seen;
1737
                $this->current = $this->last_item_seen;
1738
                $this->index = $index;
1739
            }
1740
        } else {
1741
            if ($this->debug > 2) {
1742
                error_log('In learnpath::first() - No last item seen', 0);
1743
            }
1744
            $index = 0;
1745
            // Loop through all ordered items and stop at the first item that is
1746
            // not a directory *and* that has not been completed yet.
1747
            while (!empty($this->ordered_items[$index]) &&
1748
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1749
                (
1750
                    $this->items[$this->ordered_items[$index]]->get_type() == 'dir' ||
1751
                    $this->items[$this->ordered_items[$index]]->is_done() === true
1752
                ) && $index < $this->max_ordered_items) {
1753
                $index++;
1754
            }
1755
1756
            $this->last = $this->current;
1757
            // current is
1758
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1759
            $this->index = $index;
1760
            if ($this->debug > 2) {
1761
                error_log('$index '.$index);
1762
                error_log('In learnpath::first() - No last item seen');
1763
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1764
            }
1765
        }
1766
        if ($this->debug > 2) {
1767
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1768
        }
1769
    }
1770
1771
    /**
1772
     * Gets the js library from the database.
1773
     *
1774
     * @return string The name of the javascript library to be used
1775
     */
1776
    public function get_js_lib()
1777
    {
1778
        $lib = '';
1779
        if (!empty($this->js_lib)) {
1780
            $lib = $this->js_lib;
1781
        }
1782
1783
        return $lib;
1784
    }
1785
1786
    /**
1787
     * Gets the learnpath database ID.
1788
     *
1789
     * @return int Learnpath ID in the lp table
1790
     */
1791
    public function get_id()
1792
    {
1793
        if (!empty($this->lp_id)) {
1794
            return (int) $this->lp_id;
1795
        }
1796
1797
        return 0;
1798
    }
1799
1800
    /**
1801
     * Gets the last element URL.
1802
     *
1803
     * @return string URL to load into the viewer
1804
     */
1805
    public function get_last()
1806
    {
1807
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1808
        if (count($this->ordered_items) > 0) {
1809
            $this->index = count($this->ordered_items) - 1;
1810
1811
            return $this->ordered_items[$this->index];
1812
        }
1813
1814
        return false;
1815
    }
1816
1817
    /**
1818
     * Get the last element in the first level.
1819
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1820
     *
1821
     * @return mixed
1822
     */
1823
    public function getLastInFirstLevel()
1824
    {
1825
        try {
1826
            $lastId = Database::getManager()
1827
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1828
                WHERE i.lpId = :lp AND i.parentItemId = 0 AND i.itemType != :type ORDER BY i.displayOrder DESC')
1829
                ->setMaxResults(1)
1830
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1831
                ->getSingleScalarResult();
1832
1833
            return $lastId;
1834
        } catch (Exception $exception) {
1835
            return 0;
1836
        }
1837
    }
1838
1839
    /**
1840
     * Get the learning path name by id.
1841
     *
1842
     * @param int $lpId
1843
     *
1844
     * @return mixed
1845
     */
1846
    public static function getLpNameById($lpId)
1847
    {
1848
        $em = Database::getManager();
1849
1850
        return $em->createQuery('SELECT clp.name FROM ChamiloCourseBundle:CLp clp
1851
            WHERE clp.iid = :iid')
1852
            ->setParameter('iid', $lpId)
1853
            ->getSingleScalarResult();
1854
    }
1855
1856
    /**
1857
     * Gets the navigation bar for the learnpath display screen.
1858
     *
1859
     * @param string $barId
1860
     *
1861
     * @return string The HTML string to use as a navigation bar
1862
     */
1863
    public function get_navigation_bar($barId = '')
1864
    {
1865
        if (empty($barId)) {
1866
            $barId = 'control-top';
1867
        }
1868
        $lpId = $this->lp_id;
1869
        $mycurrentitemid = $this->get_current_item_id();
1870
1871
        $reportingText = get_lang('Reporting');
1872
        $previousText = get_lang('ScormPrevious');
1873
        $nextText = get_lang('ScormNext');
1874
        $fullScreenText = get_lang('ScormExitFullScreen');
1875
1876
        $settings = api_get_configuration_value('lp_view_settings');
1877
        $display = isset($settings['display']) ? $settings['display'] : false;
1878
        $reportingIcon = '
1879
            <a class="icon-toolbar"
1880
                id="stats_link"
1881
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
1882
                onclick="window.parent.API.save_asset(); return true;"
1883
                target="content_name" title="'.$reportingText.'">
1884
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1885
            </a>';
1886
1887
        if (!empty($display)) {
1888
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1889
            if ($showReporting === false) {
1890
                $reportingIcon = '';
1891
            }
1892
        }
1893
1894
        $hideArrows = false;
1895
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1896
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1897
        }
1898
1899
        $previousIcon = '';
1900
        $nextIcon = '';
1901
        if ($hideArrows === false) {
1902
            $previousIcon = '
1903
                <a class="icon-toolbar" id="scorm-previous" href="#"
1904
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1905
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1906
                </a>';
1907
1908
            $nextIcon = '
1909
                <a class="icon-toolbar" id="scorm-next" href="#"
1910
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1911
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1912
                </a>';
1913
        }
1914
1915
        if ($this->mode === 'fullscreen') {
1916
            $navbar = '
1917
                  <span id="'.$barId.'" class="buttons">
1918
                    '.$reportingIcon.'
1919
                    '.$previousIcon.'
1920
                    '.$nextIcon.'
1921
                    <a class="icon-toolbar" id="view-embedded"
1922
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1923
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1924
                    </a>
1925
                  </span>';
1926
        } else {
1927
            $navbar = '
1928
                 <span id="'.$barId.'" class="buttons text-right">
1929
                    '.$reportingIcon.'
1930
                    '.$previousIcon.'
1931
                    '.$nextIcon.'
1932
                </span>';
1933
        }
1934
1935
        return $navbar;
1936
    }
1937
1938
    /**
1939
     * Gets the next resource in queue (url).
1940
     *
1941
     * @return string URL to load into the viewer
1942
     */
1943
    public function get_next_index()
1944
    {
1945
        // TODO
1946
        $index = $this->index;
1947
        $index++;
1948
        while (
1949
            !empty($this->ordered_items[$index]) && ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') &&
1950
            $index < $this->max_ordered_items
1951
        ) {
1952
            $index++;
1953
            if ($index == $this->max_ordered_items) {
1954
                if ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') {
1955
                    return $this->index;
1956
                }
1957
1958
                return $index;
1959
            }
1960
        }
1961
        if (empty($this->ordered_items[$index])) {
1962
            return $this->index;
1963
        }
1964
1965
        return $index;
1966
    }
1967
1968
    /**
1969
     * Gets item_id for the next element.
1970
     *
1971
     * @return int Next item (DB) ID
1972
     */
1973
    public function get_next_item_id()
1974
    {
1975
        $new_index = $this->get_next_index();
1976
        if (!empty($new_index)) {
1977
            if (isset($this->ordered_items[$new_index])) {
1978
                return $this->ordered_items[$new_index];
1979
            }
1980
        }
1981
1982
        return 0;
1983
    }
1984
1985
    /**
1986
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
1987
     *
1988
     * Generally, the package provided is in the form of a zip file, so the function
1989
     * has been written to test a zip file. If not a zip, the function will return the
1990
     * default return value: ''
1991
     *
1992
     * @param string $file_path the path to the file
1993
     * @param string $file_name the original name of the file
1994
     *
1995
     * @return string 'scorm','aicc','scorm2004','dokeos', 'error-empty-package' if the package is empty, or '' if the package cannot be recognized
1996
     */
1997
    public static function getPackageType($file_path, $file_name)
1998
    {
1999
        // Get name of the zip file without the extension.
2000
        $file_info = pathinfo($file_name);
2001
        $extension = $file_info['extension']; // Extension only.
2002
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
2003
                'dll',
2004
                'exe',
2005
            ])) {
2006
            return 'oogie';
2007
        }
2008
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
2009
                'dll',
2010
                'exe',
2011
            ])) {
2012
            return 'woogie';
2013
        }
2014
2015
        $zipFile = new PclZip($file_path);
2016
        // Check the zip content (real size and file extension).
2017
        $zipContentArray = $zipFile->listContent();
2018
        $package_type = '';
2019
        $manifest = '';
2020
        $aicc_match_crs = 0;
2021
        $aicc_match_au = 0;
2022
        $aicc_match_des = 0;
2023
        $aicc_match_cst = 0;
2024
        $countItems = 0;
2025
2026
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
2027
        if (is_array($zipContentArray)) {
2028
            $countItems = count($zipContentArray);
2029
            if ($countItems > 0) {
2030
                foreach ($zipContentArray as $thisContent) {
2031
                    if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
2032
                        // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
2033
                    } elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) {
2034
                        $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
2035
                        $package_type = 'scorm';
2036
                        break; // Exit the foreach loop.
2037
                    } elseif (
2038
                        preg_match('/aicc\//i', $thisContent['filename']) ||
2039
                        in_array(
2040
                            strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
2041
                            ['crs', 'au', 'des', 'cst']
2042
                        )
2043
                    ) {
2044
                        $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
2045
                        switch ($ext) {
2046
                            case 'crs':
2047
                                $aicc_match_crs = 1;
2048
                                break;
2049
                            case 'au':
2050
                                $aicc_match_au = 1;
2051
                                break;
2052
                            case 'des':
2053
                                $aicc_match_des = 1;
2054
                                break;
2055
                            case 'cst':
2056
                                $aicc_match_cst = 1;
2057
                                break;
2058
                            default:
2059
                                break;
2060
                        }
2061
                        //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
2062
                    } else {
2063
                        $package_type = '';
2064
                    }
2065
                }
2066
            }
2067
        }
2068
2069
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
2070
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
2071
            $package_type = 'aicc';
2072
        }
2073
2074
        // Try with chamilo course builder
2075
        if (empty($package_type)) {
2076
            // Sometimes users will try to upload an empty zip, or a zip with
2077
            // only a folder. Catch that and make the calling function aware.
2078
            // If the single file was the imsmanifest.xml, then $package_type
2079
            // would be 'scorm' and we wouldn't be here.
2080
            if ($countItems < 2) {
2081
                return 'error-empty-package';
2082
            }
2083
            $package_type = 'chamilo';
2084
        }
2085
2086
        return $package_type;
2087
    }
2088
2089
    /**
2090
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
2091
     *
2092
     * @return string URL to load into the viewer
2093
     */
2094
    public function get_previous_index()
2095
    {
2096
        $index = $this->index;
2097
        if (isset($this->ordered_items[$index - 1])) {
2098
            $index--;
2099
            while (isset($this->ordered_items[$index]) &&
2100
                ($this->items[$this->ordered_items[$index]]->get_type() == 'dir')
2101
            ) {
2102
                $index--;
2103
                if ($index < 0) {
2104
                    return $this->index;
2105
                }
2106
            }
2107
        }
2108
2109
        return $index;
2110
    }
2111
2112
    /**
2113
     * Gets item_id for the next element.
2114
     *
2115
     * @return int Previous item (DB) ID
2116
     */
2117
    public function get_previous_item_id()
2118
    {
2119
        $index = $this->get_previous_index();
2120
2121
        return $this->ordered_items[$index];
2122
    }
2123
2124
    /**
2125
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2126
     *
2127
     * @param int    $lpItemId
2128
     * @param string $autostart
2129
     *
2130
     * @return string The mediaplayer HTML
2131
     */
2132
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2133
    {
2134
        $course_id = api_get_course_int_id();
2135
        $courseInfo = api_get_course_info();
2136
        $lpItemId = (int) $lpItemId;
2137
2138
        if (empty($courseInfo) || empty($lpItemId)) {
2139
            return '';
2140
        }
2141
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
2142
2143
        if (empty($item)) {
2144
            return '';
2145
        }
2146
2147
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2148
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2149
        $itemViewId = (int) $item->db_item_view_id;
2150
2151
        // Getting all the information about the item.
2152
        $sql = "SELECT lp_view.status
2153
                FROM $tbl_lp_item as lpi
2154
                INNER JOIN $tbl_lp_item_view as lp_view
2155
                ON (lpi.iid = lp_view.lp_item_id)
2156
                WHERE
2157
                    lp_view.iid = $itemViewId AND
2158
                    lpi.iid = $lpItemId AND
2159
                    lp_view.c_id = $course_id";
2160
        $result = Database::query($sql);
2161
        $row = Database::fetch_assoc($result);
2162
        $output = '';
2163
        $audio = $item->audio;
2164
2165
        if (!empty($audio)) {
2166
            $list = $_SESSION['oLP']->get_toc();
2167
2168
            switch ($item->get_type()) {
2169
                case 'quiz':
2170
                    $type_quiz = false;
2171
                    foreach ($list as $toc) {
2172
                        if ($toc['id'] == $_SESSION['oLP']->current) {
2173
                            $type_quiz = true;
2174
                        }
2175
                    }
2176
2177
                    if ($type_quiz) {
2178
                        if ($_SESSION['oLP']->prevent_reinit == 1) {
2179
                            $autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
2180
                        } else {
2181
                            $autostart_audio = $autostart;
2182
                        }
2183
                    }
2184
                    break;
2185
                case TOOL_READOUT_TEXT:
2186
                    $autostart_audio = 'false';
2187
                    break;
2188
                default:
2189
                    $autostart_audio = 'true';
2190
            }
2191
2192
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'.$audio;
2193
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document'.$audio.'?'.api_get_cidreq();
2194
2195
            $player = Display::getMediaPlayer(
2196
                $file,
2197
                [
2198
                    'id' => 'lp_audio_media_player',
2199
                    'url' => $url,
2200
                    'autoplay' => $autostart_audio,
2201
                    'width' => '100%',
2202
                ]
2203
            );
2204
2205
            // The mp3 player.
2206
            $output = '<div id="container">';
2207
            $output .= $player;
2208
            $output .= '</div>';
2209
        }
2210
2211
        return $output;
2212
    }
2213
2214
    /**
2215
     * @param int   $studentId
2216
     * @param int   $prerequisite
2217
     * @param array $courseInfo
2218
     * @param int   $sessionId
2219
     *
2220
     * @return bool
2221
     */
2222
    public static function isBlockedByPrerequisite(
2223
        $studentId,
2224
        $prerequisite,
2225
        $courseInfo,
2226
        $sessionId
2227
    ) {
2228
        if (empty($courseInfo)) {
2229
            return false;
2230
        }
2231
2232
        $courseId = $courseInfo['real_id'];
2233
2234
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2235
        if ($allow) {
2236
            if (api_is_allowed_to_edit() ||
2237
                api_is_platform_admin(true) ||
2238
                api_is_drh() ||
2239
                api_is_coach($sessionId, $courseId, false)
2240
            ) {
2241
                return false;
2242
            }
2243
        }
2244
2245
        $isBlocked = false;
2246
        if (!empty($prerequisite)) {
2247
            $progress = self::getProgress(
2248
                $prerequisite,
2249
                $studentId,
2250
                $courseId,
2251
                $sessionId
2252
            );
2253
            if ($progress < 100) {
2254
                $isBlocked = true;
2255
            }
2256
2257
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2258
                // Block if it does not exceed minimum time
2259
                // Minimum time (in minutes) to pass the learning path
2260
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
2261
2262
                if ($accumulateWorkTime > 0) {
2263
                    // Total time in course (sum of times in learning paths from course)
2264
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
2265
2266
                    // Connect with the plugin_licences_course_session table
2267
                    // which indicates what percentage of the time applies
2268
                    // Minimum connection percentage
2269
                    $perc = 100;
2270
                    // Time from the course
2271
                    $tc = $accumulateWorkTimeTotal;
2272
2273
                    // Percentage of the learning paths
2274
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
2275
                    // Minimum time for each learning path
2276
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
2277
2278
                    // Spent time (in seconds) so far in the learning path
2279
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
2280
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
2281
2282
                    if ($lpTime < ($accumulateWorkTime * 60)) {
2283
                        $isBlocked = true;
2284
                    }
2285
                }
2286
            }
2287
        }
2288
2289
        return $isBlocked;
2290
    }
2291
2292
    /**
2293
     * Checks if the learning path is visible for student after the progress
2294
     * of its prerequisite is completed, considering the time availability and
2295
     * the LP visibility.
2296
     *
2297
     * @param int   $lp_id
2298
     * @param int   $student_id
2299
     * @param array $courseInfo
2300
     * @param int   $sessionId
2301
     *
2302
     * @return bool
2303
     */
2304
    public static function is_lp_visible_for_student(
2305
        $lp_id,
2306
        $student_id,
2307
        $courseInfo = [],
2308
        $sessionId = 0
2309
    ) {
2310
        $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
2311
        $lp_id = (int) $lp_id;
2312
        $sessionId = (int) $sessionId;
2313
2314
        if (empty($courseInfo)) {
2315
            return false;
2316
        }
2317
2318
        if (empty($sessionId)) {
2319
            $sessionId = api_get_session_id();
2320
        }
2321
2322
        $courseId = $courseInfo['real_id'];
2323
2324
        $itemInfo = api_get_item_property_info(
2325
            $courseId,
2326
            TOOL_LEARNPATH,
2327
            $lp_id,
2328
            $sessionId
2329
        );
2330
2331
        // If the item was deleted.
2332
        if (isset($itemInfo['visibility']) && $itemInfo['visibility'] == 2) {
2333
            return false;
2334
        }
2335
2336
        // @todo remove this query and load the row info as a parameter
2337
        $table = Database::get_course_table(TABLE_LP_MAIN);
2338
        // Get current prerequisite
2339
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on, category_id
2340
                FROM $table
2341
                WHERE iid = $lp_id";
2342
        $rs = Database::query($sql);
2343
        $now = time();
2344
        if (Database::num_rows($rs) > 0) {
2345
            $row = Database::fetch_array($rs, 'ASSOC');
2346
2347
            if (!empty($row['category_id'])) {
2348
                $category = self::getCategory($row['category_id']);
2349
                if (self::categoryIsVisibleForStudent($category, api_get_user_entity($student_id)) === false) {
2350
                    return false;
2351
                }
2352
            }
2353
2354
            $prerequisite = $row['prerequisite'];
2355
            $is_visible = true;
2356
2357
            $isBlocked = self::isBlockedByPrerequisite(
2358
                $student_id,
2359
                $prerequisite,
2360
                $courseInfo,
2361
                $sessionId
2362
            );
2363
2364
            if ($isBlocked) {
2365
                $is_visible = false;
2366
            }
2367
2368
            // Also check the time availability of the LP
2369
            if ($is_visible) {
2370
                // Adding visibility restrictions
2371
                if (!empty($row['publicated_on'])) {
2372
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2373
                        $is_visible = false;
2374
                    }
2375
                }
2376
                // Blocking empty start times see BT#2800
2377
                global $_custom;
2378
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2379
                    $_custom['lps_hidden_when_no_start_date']
2380
                ) {
2381
                    if (empty($row['publicated_on'])) {
2382
                        $is_visible = false;
2383
                    }
2384
                }
2385
2386
                if (!empty($row['expired_on'])) {
2387
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2388
                        $is_visible = false;
2389
                    }
2390
                }
2391
            }
2392
2393
            if ($is_visible) {
2394
                $subscriptionSettings = self::getSubscriptionSettings();
2395
2396
                // Check if the subscription users/group to a LP is ON
2397
                if (isset($row['subscribe_users']) && $row['subscribe_users'] == 1 &&
2398
                    $subscriptionSettings['allow_add_users_to_lp'] === true
2399
                ) {
2400
                    // Try group
2401
                    $is_visible = false;
2402
                    // Checking only the user visibility
2403
                    $userVisibility = api_get_item_visibility(
2404
                        $courseInfo,
2405
                        'learnpath',
2406
                        $row['id'],
2407
                        $sessionId,
2408
                        $student_id,
2409
                        'LearnpathSubscription'
2410
                    );
2411
2412
                    if ($userVisibility == 1) {
2413
                        $is_visible = true;
2414
                    } else {
2415
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
2416
                        if (!empty($userGroups)) {
2417
                            foreach ($userGroups as $groupInfo) {
2418
                                $groupId = $groupInfo['iid'];
2419
                                $userVisibility = api_get_item_visibility(
2420
                                    $courseInfo,
2421
                                    'learnpath',
2422
                                    $row['id'],
2423
                                    $sessionId,
2424
                                    null,
2425
                                    'LearnpathSubscription',
2426
                                    $groupId
2427
                                );
2428
2429
                                if ($userVisibility == 1) {
2430
                                    $is_visible = true;
2431
                                    break;
2432
                                }
2433
                            }
2434
                        }
2435
                    }
2436
                }
2437
            }
2438
2439
            return $is_visible;
2440
        }
2441
2442
        return false;
2443
    }
2444
2445
    /**
2446
     * @param int $lpId
2447
     * @param int $userId
2448
     * @param int $courseId
2449
     * @param int $sessionId
2450
     *
2451
     * @return int
2452
     */
2453
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2454
    {
2455
        $lpId = (int) $lpId;
2456
        $userId = (int) $userId;
2457
        $courseId = (int) $courseId;
2458
        $sessionId = (int) $sessionId;
2459
2460
        $sessionCondition = api_get_session_condition($sessionId);
2461
        $table = Database::get_course_table(TABLE_LP_VIEW);
2462
        $sql = "SELECT progress FROM $table
2463
                WHERE
2464
                    c_id = $courseId AND
2465
                    lp_id = $lpId AND
2466
                    user_id = $userId $sessionCondition ";
2467
        $res = Database::query($sql);
2468
2469
        $progress = 0;
2470
        if (Database::num_rows($res) > 0) {
2471
            $row = Database::fetch_array($res);
2472
            $progress = (int) $row['progress'];
2473
        }
2474
2475
        return $progress;
2476
    }
2477
2478
    /**
2479
     * @param array $lpList
2480
     * @param int   $userId
2481
     * @param int   $courseId
2482
     * @param int   $sessionId
2483
     *
2484
     * @return array
2485
     */
2486
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2487
    {
2488
        $lpList = array_map('intval', $lpList);
2489
        if (empty($lpList)) {
2490
            return [];
2491
        }
2492
2493
        $lpList = implode("','", $lpList);
2494
2495
        $userId = (int) $userId;
2496
        $courseId = (int) $courseId;
2497
        $sessionId = (int) $sessionId;
2498
2499
        $sessionCondition = api_get_session_condition($sessionId);
2500
        $table = Database::get_course_table(TABLE_LP_VIEW);
2501
        $sql = "SELECT lp_id, progress FROM $table
2502
                WHERE
2503
                    c_id = $courseId AND
2504
                    lp_id IN ('".$lpList."') AND
2505
                    user_id = $userId $sessionCondition ";
2506
        $res = Database::query($sql);
2507
2508
        if (Database::num_rows($res) > 0) {
2509
            $list = [];
2510
            while ($row = Database::fetch_array($res)) {
2511
                $list[$row['lp_id']] = $row['progress'];
2512
            }
2513
2514
            return $list;
2515
        }
2516
2517
        return [];
2518
    }
2519
2520
    /**
2521
     * Displays a progress bar
2522
     * completed so far.
2523
     *
2524
     * @param int    $percentage Progress value to display
2525
     * @param string $text_add   Text to display near the progress value
2526
     *
2527
     * @return string HTML string containing the progress bar
2528
     */
2529
    public static function get_progress_bar($percentage = -1, $text_add = '')
2530
    {
2531
        $text = $percentage.$text_add;
2532
        $output = '<div class="progress">
2533
            <div id="progress_bar_value"
2534
                class="progress-bar progress-bar-success" role="progressbar"
2535
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2536
            '.$text.'
2537
            </div>
2538
        </div>';
2539
2540
        return $output;
2541
    }
2542
2543
    /**
2544
     * @param string $mode can be '%' or 'abs'
2545
     *                     otherwise this value will be used $this->progress_bar_mode
2546
     *
2547
     * @return string
2548
     */
2549
    public function getProgressBar($mode = null)
2550
    {
2551
        list($percentage, $text_add) = $this->get_progress_bar_text($mode);
2552
2553
        return self::get_progress_bar($percentage, $text_add);
2554
    }
2555
2556
    /**
2557
     * Gets the progress bar info to display inside the progress bar.
2558
     * Also used by scorm_api.php.
2559
     *
2560
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2561
     *                     we display a number of completed elements per total elements
2562
     * @param int    $add  Additional steps to fake as completed
2563
     *
2564
     * @return array Percentage or number and symbol (% or /xx)
2565
     */
2566
    public function get_progress_bar_text($mode = '', $add = 0)
2567
    {
2568
        if (empty($mode)) {
2569
            $mode = $this->progress_bar_mode;
2570
        }
2571
        $text = '';
2572
        $percentage = 0;
2573
        // If the option to use the score as progress is set for this learning
2574
        // path, then the rules are completely different: we assume only one
2575
        // item exists and the progress of the LP depends on the score
2576
        $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
2577
        if ($scoreAsProgressSetting === true) {
2578
            $scoreAsProgress = $this->getUseScoreAsProgress();
2579
            if ($scoreAsProgress) {
2580
                // Get single item's score
2581
                $itemId = $this->get_current_item_id();
2582
                $item = $this->getItem($itemId);
2583
                $score = $item->get_score();
2584
                $maxScore = $item->get_max();
2585
                if ($mode = '%') {
2586
                    if (!empty($maxScore)) {
2587
                        $percentage = ((float) $score / (float) $maxScore) * 100;
2588
                    }
2589
                    $percentage = number_format($percentage, 0);
2590
                    $text = '%';
2591
                } else {
2592
                    $percentage = $score;
2593
                    $text = '/'.$maxScore;
2594
                }
2595
2596
                return [$percentage, $text];
2597
            }
2598
        }
2599
        // otherwise just continue the normal processing of progress
2600
        $total_items = $this->getTotalItemsCountWithoutDirs();
2601
        $completeItems = $this->get_complete_items_count();
2602
        if ($add != 0) {
2603
            $completeItems += $add;
2604
        }
2605
        if ($completeItems > $total_items) {
2606
            $completeItems = $total_items;
2607
        }
2608
        if ($mode == '%') {
2609
            if ($total_items > 0) {
2610
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2611
            }
2612
            $percentage = number_format($percentage, 0);
2613
            $text = '%';
2614
        } elseif ($mode === 'abs') {
2615
            $percentage = $completeItems;
2616
            $text = '/'.$total_items;
2617
        }
2618
2619
        return [
2620
            $percentage,
2621
            $text,
2622
        ];
2623
    }
2624
2625
    /**
2626
     * Gets the progress bar mode.
2627
     *
2628
     * @return string The progress bar mode attribute
2629
     */
2630
    public function get_progress_bar_mode()
2631
    {
2632
        if (!empty($this->progress_bar_mode)) {
2633
            return $this->progress_bar_mode;
2634
        }
2635
2636
        return '%';
2637
    }
2638
2639
    /**
2640
     * Gets the learnpath theme (remote or local).
2641
     *
2642
     * @return string Learnpath theme
2643
     */
2644
    public function get_theme()
2645
    {
2646
        if (!empty($this->theme)) {
2647
            return $this->theme;
2648
        }
2649
2650
        return '';
2651
    }
2652
2653
    /**
2654
     * Gets the learnpath session id.
2655
     *
2656
     * @return int
2657
     */
2658
    public function get_lp_session_id()
2659
    {
2660
        if (!empty($this->lp_session_id)) {
2661
            return (int) $this->lp_session_id;
2662
        }
2663
2664
        return 0;
2665
    }
2666
2667
    /**
2668
     * Gets the learnpath image.
2669
     *
2670
     * @return string Web URL of the LP image
2671
     */
2672
    public function get_preview_image()
2673
    {
2674
        if (!empty($this->preview_image)) {
2675
            return $this->preview_image;
2676
        }
2677
2678
        return '';
2679
    }
2680
2681
    /**
2682
     * @param string $size
2683
     * @param string $path_type
2684
     *
2685
     * @return bool|string
2686
     */
2687
    public function get_preview_image_path($size = null, $path_type = 'web')
2688
    {
2689
        $preview_image = $this->get_preview_image();
2690
        if (isset($preview_image) && !empty($preview_image)) {
2691
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2692
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2693
2694
            if (isset($size)) {
2695
                $info = pathinfo($preview_image);
2696
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2697
2698
                if (file_exists($image_sys_path.$image_custom_size)) {
2699
                    if ($path_type == 'web') {
2700
                        return $image_path.$image_custom_size;
2701
                    } else {
2702
                        return $image_sys_path.$image_custom_size;
2703
                    }
2704
                }
2705
            } else {
2706
                if ($path_type == 'web') {
2707
                    return $image_path.$preview_image;
2708
                } else {
2709
                    return $image_sys_path.$preview_image;
2710
                }
2711
            }
2712
        }
2713
2714
        return false;
2715
    }
2716
2717
    /**
2718
     * Gets the learnpath author.
2719
     *
2720
     * @return string LP's author
2721
     */
2722
    public function get_author()
2723
    {
2724
        if (!empty($this->author)) {
2725
            return $this->author;
2726
        }
2727
2728
        return '';
2729
    }
2730
2731
    /**
2732
     * Gets hide table of contents.
2733
     *
2734
     * @return int
2735
     */
2736
    public function getHideTableOfContents()
2737
    {
2738
        return (int) $this->hide_toc_frame;
2739
    }
2740
2741
    /**
2742
     * Generate a new prerequisites string for a given item. If this item was a sco and
2743
     * its prerequisites were strings (instead of IDs), then transform those strings into
2744
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2745
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2746
     * same rule as the scormExport() method.
2747
     *
2748
     * @param int $item_id Item ID
2749
     *
2750
     * @return string Prerequisites string ready for the export as SCORM
2751
     */
2752
    public function get_scorm_prereq_string($item_id)
2753
    {
2754
        if ($this->debug > 0) {
2755
            error_log('In learnpath::get_scorm_prereq_string()');
2756
        }
2757
        if (!is_object($this->items[$item_id])) {
2758
            return false;
2759
        }
2760
        /** @var learnpathItem $oItem */
2761
        $oItem = $this->items[$item_id];
2762
        $prereq = $oItem->get_prereq_string();
2763
2764
        if (empty($prereq)) {
2765
            return '';
2766
        }
2767
        if (preg_match('/^\d+$/', $prereq) &&
2768
            isset($this->items[$prereq]) &&
2769
            is_object($this->items[$prereq])
2770
        ) {
2771
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2772
            // then simply return it (with the ITEM_ prefix).
2773
            //return 'ITEM_' . $prereq;
2774
            return $this->items[$prereq]->ref;
2775
        } else {
2776
            if (isset($this->refs_list[$prereq])) {
2777
                // It's a simple string item from which the ID can be found in the refs list,
2778
                // so we can transform it directly to an ID for export.
2779
                return $this->items[$this->refs_list[$prereq]]->ref;
2780
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2781
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2782
            } else {
2783
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2784
                // and replace them, one by one, by the internal IDs (chamilo db)
2785
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2786
                // by a space as well.
2787
                $find = [
2788
                    '&',
2789
                    '|',
2790
                    '~',
2791
                    '=',
2792
                    '<>',
2793
                    '{',
2794
                    '}',
2795
                    '*',
2796
                    '(',
2797
                    ')',
2798
                ];
2799
                $replace = [
2800
                    ' ',
2801
                    ' ',
2802
                    ' ',
2803
                    ' ',
2804
                    ' ',
2805
                    ' ',
2806
                    ' ',
2807
                    ' ',
2808
                    ' ',
2809
                    ' ',
2810
                ];
2811
                $prereq_mod = str_replace($find, $replace, $prereq);
2812
                $ids = explode(' ', $prereq_mod);
2813
                foreach ($ids as $id) {
2814
                    $id = trim($id);
2815
                    if (isset($this->refs_list[$id])) {
2816
                        $prereq = preg_replace(
2817
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2818
                            'ITEM_'.$this->refs_list[$id],
2819
                            $prereq
2820
                        );
2821
                    }
2822
                }
2823
2824
                return $prereq;
2825
            }
2826
        }
2827
    }
2828
2829
    /**
2830
     * Returns the XML DOM document's node.
2831
     *
2832
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2833
     * @param string   $id       The identifier to look for
2834
     *
2835
     * @return mixed The reference to the element found with that identifier. False if not found
2836
     */
2837
    public function get_scorm_xml_node(&$children, $id)
2838
    {
2839
        for ($i = 0; $i < $children->length; $i++) {
2840
            $item_temp = $children->item($i);
2841
            if ($item_temp->nodeName == 'item') {
2842
                if ($item_temp->getAttribute('identifier') == $id) {
2843
                    return $item_temp;
2844
                }
2845
            }
2846
            $subchildren = $item_temp->childNodes;
2847
            if ($subchildren && $subchildren->length > 0) {
2848
                $val = $this->get_scorm_xml_node($subchildren, $id);
2849
                if (is_object($val)) {
2850
                    return $val;
2851
                }
2852
            }
2853
        }
2854
2855
        return false;
2856
    }
2857
2858
    /**
2859
     * Gets the status list for all LP's items.
2860
     *
2861
     * @return array Array of [index] => [item ID => current status]
2862
     */
2863
    public function get_items_status_list()
2864
    {
2865
        $list = [];
2866
        foreach ($this->ordered_items as $item_id) {
2867
            $list[] = [
2868
                $item_id => $this->items[$item_id]->get_status(),
2869
            ];
2870
        }
2871
2872
        return $list;
2873
    }
2874
2875
    /**
2876
     * Return the number of interactions for the given learnpath Item View ID.
2877
     * This method can be used as static.
2878
     *
2879
     * @param int $lp_iv_id  Item View ID
2880
     * @param int $course_id course id
2881
     *
2882
     * @return int
2883
     */
2884
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2885
    {
2886
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2887
        $lp_iv_id = (int) $lp_iv_id;
2888
        $course_id = (int) $course_id;
2889
2890
        $sql = "SELECT count(*) FROM $table
2891
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2892
        $res = Database::query($sql);
2893
        $num = 0;
2894
        if (Database::num_rows($res)) {
2895
            $row = Database::fetch_array($res);
2896
            $num = $row[0];
2897
        }
2898
2899
        return $num;
2900
    }
2901
2902
    /**
2903
     * Return the interactions as an array for the given lp_iv_id.
2904
     * This method can be used as static.
2905
     *
2906
     * @param int $lp_iv_id Learnpath Item View ID
2907
     *
2908
     * @return array
2909
     *
2910
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2911
     */
2912
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2913
    {
2914
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2915
        $list = [];
2916
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2917
        $lp_iv_id = (int) $lp_iv_id;
2918
2919
        if (empty($lp_iv_id) || empty($course_id)) {
2920
            return [];
2921
        }
2922
2923
        $sql = "SELECT * FROM $table
2924
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2925
                ORDER BY order_id ASC";
2926
        $res = Database::query($sql);
2927
        $num = Database::num_rows($res);
2928
        if ($num > 0) {
2929
            $list[] = [
2930
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2931
                'id' => api_htmlentities(get_lang('InteractionID'), ENT_QUOTES),
2932
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2933
                'time' => api_htmlentities(get_lang('TimeFinished'), ENT_QUOTES),
2934
                'correct_responses' => api_htmlentities(get_lang('CorrectAnswers'), ENT_QUOTES),
2935
                'student_response' => api_htmlentities(get_lang('StudentResponse'), ENT_QUOTES),
2936
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2937
                'latency' => api_htmlentities(get_lang('LatencyTimeSpent'), ENT_QUOTES),
2938
                'student_response_formatted' => '',
2939
            ];
2940
            while ($row = Database::fetch_array($res)) {
2941
                $studentResponseFormatted = urldecode($row['student_response']);
2942
                $content_student_response = explode('__|', $studentResponseFormatted);
2943
                if (count($content_student_response) > 0) {
2944
                    if (count($content_student_response) >= 3) {
2945
                        // Pop the element off the end of array.
2946
                        array_pop($content_student_response);
2947
                    }
2948
                    $studentResponseFormatted = implode(',', $content_student_response);
2949
                }
2950
2951
                $list[] = [
2952
                    'order_id' => $row['order_id'] + 1,
2953
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2954
                    'type' => $row['interaction_type'],
2955
                    'time' => $row['completion_time'],
2956
                    'correct_responses' => '', // Hide correct responses from students.
2957
                    'student_response' => $row['student_response'],
2958
                    'result' => $row['result'],
2959
                    'latency' => $row['latency'],
2960
                    'student_response_formatted' => $studentResponseFormatted,
2961
                ];
2962
            }
2963
        }
2964
2965
        return $list;
2966
    }
2967
2968
    /**
2969
     * Return the number of objectives for the given learnpath Item View ID.
2970
     * This method can be used as static.
2971
     *
2972
     * @param int $lp_iv_id  Item View ID
2973
     * @param int $course_id Course ID
2974
     *
2975
     * @return int Number of objectives
2976
     */
2977
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2978
    {
2979
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2980
        $course_id = (int) $course_id;
2981
        $lp_iv_id = (int) $lp_iv_id;
2982
        $sql = "SELECT count(*) FROM $table
2983
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2984
        //@todo seems that this always returns 0
2985
        $res = Database::query($sql);
2986
        $num = 0;
2987
        if (Database::num_rows($res)) {
2988
            $row = Database::fetch_array($res);
2989
            $num = $row[0];
2990
        }
2991
2992
        return $num;
2993
    }
2994
2995
    /**
2996
     * Return the objectives as an array for the given lp_iv_id.
2997
     * This method can be used as static.
2998
     *
2999
     * @param int $lpItemViewId Learnpath Item View ID
3000
     * @param int $course_id
3001
     *
3002
     * @return array
3003
     *
3004
     * @todo    Translate labels
3005
     */
3006
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
3007
    {
3008
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
3009
        $lpItemViewId = (int) $lpItemViewId;
3010
3011
        if (empty($course_id) || empty($lpItemViewId)) {
3012
            return [];
3013
        }
3014
3015
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3016
        $sql = "SELECT * FROM $table
3017
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
3018
                ORDER BY order_id ASC";
3019
        $res = Database::query($sql);
3020
        $num = Database::num_rows($res);
3021
        $list = [];
3022
        if ($num > 0) {
3023
            $list[] = [
3024
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
3025
                'objective_id' => api_htmlentities(get_lang('ObjectiveID'), ENT_QUOTES),
3026
                'score_raw' => api_htmlentities(get_lang('ObjectiveRawScore'), ENT_QUOTES),
3027
                'score_max' => api_htmlentities(get_lang('ObjectiveMaxScore'), ENT_QUOTES),
3028
                'score_min' => api_htmlentities(get_lang('ObjectiveMinScore'), ENT_QUOTES),
3029
                'status' => api_htmlentities(get_lang('ObjectiveStatus'), ENT_QUOTES),
3030
            ];
3031
            while ($row = Database::fetch_array($res)) {
3032
                $list[] = [
3033
                    'order_id' => $row['order_id'] + 1,
3034
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
3035
                    'score_raw' => $row['score_raw'],
3036
                    'score_max' => $row['score_max'],
3037
                    'score_min' => $row['score_min'],
3038
                    'status' => $row['status'],
3039
                ];
3040
            }
3041
        }
3042
3043
        return $list;
3044
    }
3045
3046
    /**
3047
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
3048
     * used by get_html_toc() to be ready to display.
3049
     *
3050
     * @return array TOC as a table with 4 elements per row: title, link, status and level
3051
     */
3052
    public function get_toc()
3053
    {
3054
        $toc = [];
3055
        foreach ($this->ordered_items as $item_id) {
3056
            // TODO: Change this link generation and use new function instead.
3057
            $toc[] = [
3058
                'id' => $item_id,
3059
                'title' => $this->items[$item_id]->get_title(),
3060
                'status' => $this->items[$item_id]->get_status(),
3061
                'level' => $this->items[$item_id]->get_level(),
3062
                'type' => $this->items[$item_id]->get_type(),
3063
                'description' => $this->items[$item_id]->get_description(),
3064
                'path' => $this->items[$item_id]->get_path(),
3065
                'parent' => $this->items[$item_id]->get_parent(),
3066
            ];
3067
        }
3068
3069
        return $toc;
3070
    }
3071
3072
    /**
3073
     * Returns the CSS class name associated with a given item status.
3074
     *
3075
     * @param $status string an item status
3076
     *
3077
     * @return string CSS class name
3078
     */
3079
    public static function getStatusCSSClassName($status)
3080
    {
3081
        if (array_key_exists($status, self::STATUS_CSS_CLASS_NAME)) {
3082
            return self::STATUS_CSS_CLASS_NAME[$status];
3083
        }
3084
3085
        return '';
3086
    }
3087
3088
    /**
3089
     * Generate the tree of contents for this learnpath as an associative array tree
3090
     * with keys id, title, status, type, description, path, parent_id, children
3091
     * (title and descriptions as secured)
3092
     * and clues for CSS class composition:
3093
     *  - booleans is_current, is_parent_of_current, is_chapter
3094
     *  - string status_css_class_name.
3095
     *
3096
     * @param $parentId int restrict returned list to children of this parent
3097
     *
3098
     * @return array TOC as a table
3099
     */
3100
    public function getTOCTree($parentId = 0)
3101
    {
3102
        $toc = [];
3103
        $currentItemId = $this->get_current_item_id();
3104
3105
        foreach ($this->ordered_items as $itemId) {
3106
            $item = $this->items[$itemId];
3107
            if ($item->get_parent() == $parentId) {
3108
                $title = $item->get_title();
3109
                if (empty($title)) {
3110
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $itemId);
3111
                }
3112
3113
                $itemData = [
3114
                    'id' => $itemId,
3115
                    'title' => Security::remove_XSS($title),
3116
                    'status' => $item->get_status(),
3117
                    'level' => $item->get_level(), // FIXME should not be needed
3118
                    'type' => $item->get_type(),
3119
                    'description' => Security::remove_XSS($item->get_description()),
3120
                    'path' => $item->get_path(),
3121
                    'parent_id' => $item->get_parent(),
3122
                    'children' => $this->getTOCTree($itemId),
3123
                    'is_current' => ($itemId == $currentItemId),
3124
                    'is_parent_of_current' => false,
3125
                    'is_chapter' => in_array($item->get_type(), self::getChapterTypes()),
3126
                    'status_css_class_name' => $this->getStatusCSSClassName($item->get_status()),
3127
                    'current_id' => $currentItemId, // FIXME should not be needed, not a property of item
3128
                ];
3129
3130
                if (!empty($itemData['children'])) {
3131
                    foreach ($itemData['children'] as $child) {
3132
                        if ($child['is_current'] || $child['is_parent_of_current']) {
3133
                            $itemData['is_parent_of_current'] = true;
3134
                            break;
3135
                        }
3136
                    }
3137
                }
3138
3139
                $toc[] = $itemData;
3140
            }
3141
        }
3142
3143
        return $toc;
3144
    }
3145
3146
    /**
3147
     * Generate and return the table of contents for this learnpath. The JS
3148
     * table returned is used inside of scorm_api.php.
3149
     *
3150
     * @param string $varname
3151
     *
3152
     * @return string A JS array variable construction
3153
     */
3154
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
3155
    {
3156
        $toc = $varname.' = new Array();';
3157
        foreach ($this->ordered_items as $item_id) {
3158
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
3159
        }
3160
3161
        return $toc;
3162
    }
3163
3164
    /**
3165
     * Gets the learning path type.
3166
     *
3167
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
3168
     *
3169
     * @return mixed Type ID or name, depending on the parameter
3170
     */
3171
    public function get_type($get_name = false)
3172
    {
3173
        $res = false;
3174
        if (!empty($this->type) && (!$get_name)) {
3175
            $res = $this->type;
3176
        }
3177
3178
        return $res;
3179
    }
3180
3181
    /**
3182
     * Gets the learning path type as static method.
3183
     *
3184
     * @param int $lp_id
3185
     *
3186
     * @return mixed Returns the lp_type: 1 = Chamilo lms / 2 = SCORM
3187
     */
3188
    public static function get_type_static($lp_id = 0)
3189
    {
3190
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3191
        $lp_id = (int) $lp_id;
3192
        $sql = "SELECT lp_type FROM $tbl_lp
3193
                WHERE iid = $lp_id";
3194
        $res = Database::query($sql);
3195
        if ($res === false) {
3196
            return null;
3197
        }
3198
        if (Database::num_rows($res) <= 0) {
3199
            return null;
3200
        }
3201
        $row = Database::fetch_array($res);
3202
3203
        return $row['lp_type'];
3204
    }
3205
3206
    /**
3207
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3208
     * This method can be used as abstract and is recursive.
3209
     *
3210
     * @param int $lp        Learnpath ID
3211
     * @param int $parent    Parent ID of the items to look for
3212
     * @param int $course_id
3213
     *
3214
     * @return array Ordered list of item IDs (empty array on error)
3215
     */
3216
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3217
    {
3218
        if (empty($course_id)) {
3219
            $course_id = api_get_course_int_id();
3220
        } else {
3221
            $course_id = (int) $course_id;
3222
        }
3223
        $list = [];
3224
3225
        if (empty($lp)) {
3226
            return $list;
3227
        }
3228
3229
        $lp = (int) $lp;
3230
        $parent = (int) $parent;
3231
3232
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3233
        $sql = "SELECT iid FROM $tbl_lp_item
3234
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3235
                ORDER BY display_order";
3236
3237
        $res = Database::query($sql);
3238
        while ($row = Database::fetch_array($res)) {
3239
            $sublist = self::get_flat_ordered_items_list(
3240
                $lp,
3241
                $row['iid'],
3242
                $course_id
3243
            );
3244
            $list[] = $row['iid'];
3245
            foreach ($sublist as $item) {
3246
                $list[] = $item;
3247
            }
3248
        }
3249
3250
        return $list;
3251
    }
3252
3253
    /**
3254
     * @return array
3255
     */
3256
    public static function getChapterTypes()
3257
    {
3258
        return [
3259
            'dir',
3260
        ];
3261
    }
3262
3263
    /**
3264
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3265
     *
3266
     * @param $tree
3267
     *
3268
     * @return array HTML TOC ready to display
3269
     */
3270
    public function getParentToc($tree)
3271
    {
3272
        if (empty($tree)) {
3273
            $tree = $this->get_toc();
3274
        }
3275
        $dirTypes = self::getChapterTypes();
3276
        $myCurrentId = $this->get_current_item_id();
3277
        $listParent = [];
3278
        $listChildren = [];
3279
        $listNotParent = [];
3280
        $list = [];
3281
        foreach ($tree as $subtree) {
3282
            if (in_array($subtree['type'], $dirTypes)) {
3283
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3284
                $subtree['children'] = $listChildren;
3285
                if (!empty($subtree['children'])) {
3286
                    foreach ($subtree['children'] as $subItem) {
3287
                        if ($subItem['id'] == $this->current) {
3288
                            $subtree['parent_current'] = 'in';
3289
                            $subtree['current'] = 'on';
3290
                        }
3291
                    }
3292
                }
3293
                $listParent[] = $subtree;
3294
            }
3295
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
3296
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3297
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3298
                }
3299
3300
                $title = Security::remove_XSS($subtree['title']);
3301
                unset($subtree['title']);
3302
3303
                if (empty($title)) {
3304
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3305
                }
3306
                $classStyle = null;
3307
                if ($subtree['id'] == $this->current) {
3308
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3309
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3310
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3311
                }
3312
                $subtree['title'] = $title;
3313
                $subtree['class'] = $classStyle.' '.$cssStatus;
3314
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3315
                $subtree['current_id'] = $myCurrentId;
3316
                $listNotParent[] = $subtree;
3317
            }
3318
        }
3319
3320
        $list['are_parents'] = $listParent;
3321
        $list['not_parents'] = $listNotParent;
3322
3323
        return $list;
3324
    }
3325
3326
    /**
3327
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3328
     *
3329
     * @param array $tree
3330
     * @param int   $id
3331
     * @param bool  $parent
3332
     *
3333
     * @return array HTML TOC ready to display
3334
     */
3335
    public function getChildrenToc($tree, $id, $parent = true)
3336
    {
3337
        if (empty($tree)) {
3338
            $tree = $this->get_toc();
3339
        }
3340
3341
        $dirTypes = self::getChapterTypes();
3342
        $currentItemId = $this->get_current_item_id();
3343
        $list = [];
3344
3345
        foreach ($tree as $subtree) {
3346
            $subtree['tree'] = null;
3347
3348
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3349
                if ($subtree['id'] == $this->current) {
3350
                    $subtree['current'] = 'active';
3351
                } else {
3352
                    $subtree['current'] = null;
3353
                }
3354
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3355
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3356
                }
3357
3358
                $title = Security::remove_XSS($subtree['title']);
3359
                unset($subtree['title']);
3360
                if (empty($title)) {
3361
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3362
                }
3363
3364
                $classStyle = null;
3365
                if ($subtree['id'] == $this->current) {
3366
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3367
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3368
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3369
                }
3370
3371
                if (in_array($subtree['type'], $dirTypes)) {
3372
                    $subtree['title'] = stripslashes($title);
3373
                } else {
3374
                    $subtree['title'] = $title;
3375
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3376
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3377
                    $subtree['current_id'] = $currentItemId;
3378
                }
3379
                $list[] = $subtree;
3380
            }
3381
        }
3382
3383
        return $list;
3384
    }
3385
3386
    /**
3387
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3388
     *
3389
     * @param array $toc_list
3390
     *
3391
     * @return array HTML TOC ready to display
3392
     */
3393
    public function getListArrayToc($toc_list = [])
3394
    {
3395
        if (empty($toc_list)) {
3396
            $toc_list = $this->get_toc();
3397
        }
3398
        // Temporary variables.
3399
        $currentItemId = $this->get_current_item_id();
3400
        $list = [];
3401
        $arrayList = [];
3402
3403
        foreach ($toc_list as $item) {
3404
            $list['id'] = $item['id'];
3405
            $list['status'] = $item['status'];
3406
            $cssStatus = null;
3407
3408
            if (array_key_exists($item['status'], self::STATUS_CSS_CLASS_NAME)) {
3409
                $cssStatus = self::STATUS_CSS_CLASS_NAME[$item['status']];
3410
            }
3411
3412
            $classStyle = ' ';
3413
            $dirTypes = self::getChapterTypes();
3414
3415
            if (in_array($item['type'], $dirTypes)) {
3416
                $classStyle = 'scorm_item_section ';
3417
            }
3418
            if ($item['id'] == $this->current) {
3419
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3420
            } elseif (!in_array($item['type'], $dirTypes)) {
3421
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3422
            }
3423
            $title = $item['title'];
3424
            if (empty($title)) {
3425
                $title = self::rl_get_resource_name(
3426
                    api_get_course_id(),
3427
                    $this->get_id(),
3428
                    $item['id']
3429
                );
3430
            }
3431
            $title = Security::remove_XSS($item['title']);
3432
3433
            if (empty($item['description'])) {
3434
                $list['description'] = $title;
3435
            } else {
3436
                $list['description'] = $item['description'];
3437
            }
3438
3439
            $list['class'] = $classStyle.' '.$cssStatus;
3440
            $list['level'] = $item['level'];
3441
            $list['type'] = $item['type'];
3442
3443
            if (in_array($item['type'], $dirTypes)) {
3444
                $list['css_level'] = 'level_'.$item['level'];
3445
            } else {
3446
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3447
            }
3448
3449
            if (in_array($item['type'], $dirTypes)) {
3450
                $list['title'] = stripslashes($title);
3451
            } else {
3452
                $list['title'] = stripslashes($title);
3453
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3454
                $list['current_id'] = $currentItemId;
3455
            }
3456
            $arrayList[] = $list;
3457
        }
3458
3459
        return $arrayList;
3460
    }
3461
3462
    /**
3463
     * Returns an HTML-formatted string ready to display with teacher buttons
3464
     * in LP view menu.
3465
     *
3466
     * @return string HTML TOC ready to display
3467
     */
3468
    public function get_teacher_toc_buttons()
3469
    {
3470
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3471
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3472
        $html = '';
3473
        if ($isAllow && $hideIcons == false) {
3474
            if ($this->get_lp_session_id() == api_get_session_id()) {
3475
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3476
                $html .= '<div class="btn-group">';
3477
                $html .= "<a class='btn btn-sm btn-default' href='lp_controller.php?".api_get_cidreq()."&action=build&lp_id=".$this->lp_id."&isStudentView=false' target='_parent'>".
3478
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3479
                $html .= "<a class='btn btn-sm btn-default' href='lp_controller.php?".api_get_cidreq()."&action=add_item&type=step&lp_id=".$this->lp_id."&isStudentView=false' target='_parent'>".
3480
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3481
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3482
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3483
                $html .= '</div>';
3484
                $html .= '</div>';
3485
            }
3486
        }
3487
3488
        return $html;
3489
    }
3490
3491
    /**
3492
     * Gets the learnpath maker name - generally the editor's name.
3493
     *
3494
     * @return string Learnpath maker name
3495
     */
3496
    public function get_maker()
3497
    {
3498
        if (!empty($this->maker)) {
3499
            return $this->maker;
3500
        }
3501
3502
        return '';
3503
    }
3504
3505
    /**
3506
     * Gets the learnpath name/title.
3507
     *
3508
     * @return string Learnpath name/title
3509
     */
3510
    public function get_name()
3511
    {
3512
        if (!empty($this->name)) {
3513
            return $this->name;
3514
        }
3515
3516
        return 'N/A';
3517
    }
3518
3519
    /**
3520
     * @return string
3521
     */
3522
    public function getNameNoTags()
3523
    {
3524
        return strip_tags($this->get_name());
3525
    }
3526
3527
    /**
3528
     * Gets a link to the resource from the present location, depending on item ID.
3529
     *
3530
     * @param string $type         Type of link expected
3531
     * @param int    $item_id      Learnpath item ID
3532
     * @param bool   $provided_toc
3533
     *
3534
     * @return string $provided_toc Link to the lp_item resource
3535
     */
3536
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
3537
    {
3538
        $course_id = $this->get_course_int_id();
3539
        $item_id = (int) $item_id;
3540
3541
        if (empty($item_id)) {
3542
            $item_id = $this->get_current_item_id();
3543
3544
            if (empty($item_id)) {
3545
                //still empty, this means there was no item_id given and we are not in an object context or
3546
                //the object property is empty, return empty link
3547
                $this->first();
3548
3549
                return '';
3550
            }
3551
        }
3552
3553
        $file = '';
3554
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3555
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3556
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3557
3558
        $sql = "SELECT
3559
                    l.lp_type as ltype,
3560
                    l.path as lpath,
3561
                    li.item_type as litype,
3562
                    li.path as lipath,
3563
                    li.parameters as liparams
3564
        		FROM $lp_table l
3565
                INNER JOIN $lp_item_table li
3566
                ON (li.lp_id = l.iid)
3567
        		WHERE
3568
        		    li.iid = $item_id
3569
        		";
3570
        $res = Database::query($sql);
3571
        if (Database::num_rows($res) > 0) {
3572
            $row = Database::fetch_array($res);
3573
            $lp_type = $row['ltype'];
3574
            $lp_path = $row['lpath'];
3575
            $lp_item_type = $row['litype'];
3576
            $lp_item_path = $row['lipath'];
3577
            $lp_item_params = $row['liparams'];
3578
3579
            if (empty($lp_item_params) && strpos($lp_item_path, '?') !== false) {
3580
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3581
            }
3582
            $sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3583
            if ($type === 'http') {
3584
                //web path
3585
                $course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3586
            } else {
3587
                $course_path = $sys_course_path; //system path
3588
            }
3589
3590
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3591
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3592
            if (in_array(
3593
                $lp_item_type,
3594
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3595
            )
3596
            ) {
3597
                $lp_type = 1;
3598
            }
3599
3600
            // Now go through the specific cases to get the end of the path
3601
            // @todo Use constants instead of int values.
3602
            switch ($lp_type) {
3603
                case 1:
3604
                    $file = self::rl_get_resource_link_for_learnpath(
3605
                        $course_id,
3606
                        $this->get_id(),
3607
                        $item_id,
3608
                        $this->get_view_id(),
3609
                        $this->get_lp_session_id()
3610
                    );
3611
                    switch ($lp_item_type) {
3612
                        case 'document':
3613
                            // Shows a button to download the file instead of just downloading the file directly.
3614
                            $documentPathInfo = pathinfo($file);
3615
                            if (isset($documentPathInfo['extension'])) {
3616
                                $parsed = parse_url($documentPathInfo['extension']);
3617
                                if (isset($parsed['path'])) {
3618
                                    $extension = $parsed['path'];
3619
                                    $extensionsToDownload = [
3620
                                        'zip',
3621
                                        'ppt',
3622
                                        'pptx',
3623
                                        'ods',
3624
                                        'xlsx',
3625
                                        'xls',
3626
                                        'csv',
3627
                                        'doc',
3628
                                        'docx',
3629
                                        'dot',
3630
                                    ];
3631
3632
                                    if (in_array($extension, $extensionsToDownload)) {
3633
                                        $file = api_get_path(WEB_CODE_PATH).
3634
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
3635
                                    }
3636
                                }
3637
                            }
3638
                            break;
3639
                        case 'dir':
3640
                            $file = 'lp_content.php?type=dir&'.api_get_cidreq();
3641
                            break;
3642
                        case 'link':
3643
                            if (Link::is_youtube_link($file)) {
3644
                                $src = Link::get_youtube_video_id($file);
3645
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3646
                            } elseif (Link::isVimeoLink($file)) {
3647
                                $src = Link::getVimeoLinkId($file);
3648
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3649
                            } else {
3650
                                // If the current site is HTTPS and the link is
3651
                                // HTTP, browsers will refuse opening the link
3652
                                $urlId = api_get_current_access_url_id();
3653
                                $url = api_get_access_url($urlId, false);
3654
                                $protocol = substr($url['url'], 0, 5);
3655
                                if ($protocol === 'https') {
3656
                                    $linkProtocol = substr($file, 0, 5);
3657
                                    if ($linkProtocol === 'http:') {
3658
                                        //this is the special intervention case
3659
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3660
                                    }
3661
                                }
3662
                            }
3663
                            break;
3664
                        case 'quiz':
3665
                            // Check how much attempts of a exercise exits in lp
3666
                            $lp_item_id = $this->get_current_item_id();
3667
                            $lp_view_id = $this->get_view_id();
3668
3669
                            $prevent_reinit = null;
3670
                            if (isset($this->items[$this->current])) {
3671
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3672
                            }
3673
3674
                            if (empty($provided_toc)) {
3675
                                if ($this->debug > 0) {
3676
                                    error_log('In learnpath::get_link() Loading get_toc ', 0);
3677
                                }
3678
                                $list = $this->get_toc();
3679
                            } else {
3680
                                if ($this->debug > 0) {
3681
                                    error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
3682
                                }
3683
                                $list = $provided_toc;
3684
                            }
3685
3686
                            $type_quiz = false;
3687
                            foreach ($list as $toc) {
3688
                                if ($toc['id'] == $lp_item_id && $toc['type'] === 'quiz') {
3689
                                    $type_quiz = true;
3690
                                }
3691
                            }
3692
3693
                            if ($type_quiz) {
3694
                                $lp_item_id = (int) $lp_item_id;
3695
                                $lp_view_id = (int) $lp_view_id;
3696
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3697
                                        WHERE
3698
                                            c_id = $course_id AND
3699
                                            lp_item_id='".$lp_item_id."' AND
3700
                                            lp_view_id ='".$lp_view_id."' AND
3701
                                            status='completed'";
3702
                                $result = Database::query($sql);
3703
                                $row_count = Database:: fetch_row($result);
3704
                                $count_item_view = (int) $row_count[0];
3705
                                $not_multiple_attempt = 0;
3706
                                if ($prevent_reinit === 1 && $count_item_view > 0) {
3707
                                    $not_multiple_attempt = 1;
3708
                                }
3709
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3710
                            }
3711
                            break;
3712
                    }
3713
3714
                    $tmp_array = explode('/', $file);
3715
                    $document_name = $tmp_array[count($tmp_array) - 1];
3716
                    if (strpos($document_name, '_DELETED_')) {
3717
                        $file = 'blank.php?error=document_deleted';
3718
                    }
3719
                    break;
3720
                case 2:
3721
                    if ($this->debug > 2) {
3722
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3723
                    }
3724
3725
                    if ($lp_item_type != 'dir') {
3726
                        // Quite complex here:
3727
                        // We want to make sure 'http://' (and similar) links can
3728
                        // be loaded as is (withouth the Chamilo path in front) but
3729
                        // some contents use this form: resource.htm?resource=http://blablabla
3730
                        // which means we have to find a protocol at the path's start, otherwise
3731
                        // it should not be considered as an external URL.
3732
                        // if ($this->prerequisites_match($item_id)) {
3733
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3734
                            if ($this->debug > 2) {
3735
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3736
                            }
3737
                            // Distant url, return as is.
3738
                            $file = $lp_item_path;
3739
                        } else {
3740
                            if ($this->debug > 2) {
3741
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3742
                            }
3743
                            // Prevent getting untranslatable urls.
3744
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3745
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3746
                            // Prepare the path.
3747
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3748
                            // TODO: Fix this for urls with protocol header.
3749
                            $file = str_replace('//', '/', $file);
3750
                            $file = str_replace(':/', '://', $file);
3751
                            if (substr($lp_path, -1) == '/') {
3752
                                $lp_path = substr($lp_path, 0, -1);
3753
                            }
3754
3755
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3756
                                // if file not found.
3757
                                $decoded = html_entity_decode($lp_item_path);
3758
                                list($decoded) = explode('?', $decoded);
3759
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3760
                                    $file = self::rl_get_resource_link_for_learnpath(
3761
                                        $course_id,
3762
                                        $this->get_id(),
3763
                                        $item_id,
3764
                                        $this->get_view_id()
3765
                                    );
3766
                                    if (empty($file)) {
3767
                                        $file = 'blank.php?error=document_not_found';
3768
                                    } else {
3769
                                        $tmp_array = explode('/', $file);
3770
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3771
                                        if (strpos($document_name, '_DELETED_')) {
3772
                                            $file = 'blank.php?error=document_deleted';
3773
                                        } else {
3774
                                            $file = 'blank.php?error=document_not_found';
3775
                                        }
3776
                                    }
3777
                                } else {
3778
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3779
                                }
3780
                            }
3781
                        }
3782
3783
                        // We want to use parameters if they were defined in the imsmanifest
3784
                        if (strpos($file, 'blank.php') === false) {
3785
                            $lp_item_params = ltrim($lp_item_params, '?');
3786
                            $file .= (strstr($file, '?') === false ? '?' : '').$lp_item_params;
3787
                        }
3788
                    } else {
3789
                        $file = 'lp_content.php?type=dir&'.api_get_cidreq();
3790
                    }
3791
                    break;
3792
                case 3:
3793
                    if ($this->debug > 2) {
3794
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3795
                    }
3796
                    // Formatting AICC HACP append URL.
3797
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3798
                    if (!empty($lp_item_params)) {
3799
                        $aicc_append .= $lp_item_params.'&';
3800
                    }
3801
                    if ($lp_item_type != 'dir') {
3802
                        // Quite complex here:
3803
                        // We want to make sure 'http://' (and similar) links can
3804
                        // be loaded as is (withouth the Chamilo path in front) but
3805
                        // some contents use this form: resource.htm?resource=http://blablabla
3806
                        // which means we have to find a protocol at the path's start, otherwise
3807
                        // it should not be considered as an external URL.
3808
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3809
                            if ($this->debug > 2) {
3810
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3811
                            }
3812
                            // Distant url, return as is.
3813
                            $file = $lp_item_path;
3814
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3815
                            /*
3816
                            if (stristr($file,'<servername>') !== false) {
3817
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3818
                            }
3819
                            */
3820
                            if (stripos($file, '<servername>') !== false) {
3821
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3822
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3823
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3824
                            }
3825
3826
                            $file .= $aicc_append;
3827
                        } else {
3828
                            if ($this->debug > 2) {
3829
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3830
                            }
3831
                            // Prevent getting untranslatable urls.
3832
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3833
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3834
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3835
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3836
                            // TODO: Fix this for urls with protocol header.
3837
                            $file = str_replace('//', '/', $file);
3838
                            $file = str_replace(':/', '://', $file);
3839
                            $file .= $aicc_append;
3840
                        }
3841
                    } else {
3842
                        $file = 'lp_content.php?type=dir&'.api_get_cidreq();
3843
                    }
3844
                    break;
3845
                case 4:
3846
                    break;
3847
                default:
3848
                    break;
3849
            }
3850
            // Replace &amp; by & because &amp; will break URL with params
3851
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3852
        }
3853
        if ($this->debug > 2) {
3854
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3855
        }
3856
3857
        return $file;
3858
    }
3859
3860
    /**
3861
     * Gets the latest usable view or generate a new one.
3862
     *
3863
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3864
     * @param int $userId      The user ID, as $this->get_user_id() is not always available
3865
     *
3866
     * @return int DB lp_view id
3867
     */
3868
    public function get_view($attempt_num = 0, $userId = null)
3869
    {
3870
        $search = '';
3871
        // Use $attempt_num to enable multi-views management (disabled so far).
3872
        if ($attempt_num != 0 && intval(strval($attempt_num)) == $attempt_num) {
3873
            $search = 'AND view_count = '.$attempt_num;
3874
        }
3875
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3876
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3877
3878
        $course_id = api_get_course_int_id();
3879
        $sessionId = api_get_session_id();
3880
3881
        // Check user ID.
3882
        if (empty($userId)) {
3883
            if (empty($this->get_user_id())) {
3884
                $this->error = 'User ID is empty in learnpath::get_view()';
3885
3886
                return null;
3887
            } else {
3888
                $userId = $this->get_user_id();
3889
            }
3890
        }
3891
3892
        $sql = "SELECT iid, view_count FROM $lp_view_table
3893
        		WHERE
3894
        		    c_id = $course_id AND
3895
        		    lp_id = ".$this->get_id()." AND
3896
        		    user_id = ".$userId." AND
3897
        		    session_id = $sessionId
3898
        		    $search
3899
                ORDER BY view_count DESC";
3900
        $res = Database::query($sql);
3901
        if (Database::num_rows($res) > 0) {
3902
            $row = Database::fetch_array($res);
3903
            $this->lp_view_id = $row['iid'];
3904
        } elseif (!api_is_invitee()) {
3905
            // There is no database record, create one.
3906
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3907
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3908
            Database::query($sql);
3909
            $id = Database::insert_id();
3910
            $this->lp_view_id = $id;
3911
3912
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
3913
            Database::query($sql);
3914
        }
3915
3916
        return $this->lp_view_id;
3917
    }
3918
3919
    /**
3920
     * Gets the current view id.
3921
     *
3922
     * @return int View ID (from lp_view)
3923
     */
3924
    public function get_view_id()
3925
    {
3926
        if (!empty($this->lp_view_id)) {
3927
            return (int) $this->lp_view_id;
3928
        }
3929
3930
        return 0;
3931
    }
3932
3933
    /**
3934
     * Gets the update queue.
3935
     *
3936
     * @return array Array containing IDs of items to be updated by JavaScript
3937
     */
3938
    public function get_update_queue()
3939
    {
3940
        return $this->update_queue;
3941
    }
3942
3943
    /**
3944
     * Gets the user ID.
3945
     *
3946
     * @return int User ID
3947
     */
3948
    public function get_user_id()
3949
    {
3950
        if (!empty($this->user_id)) {
3951
            return (int) $this->user_id;
3952
        }
3953
3954
        return false;
3955
    }
3956
3957
    /**
3958
     * Checks if any of the items has an audio element attached.
3959
     *
3960
     * @return bool True or false
3961
     */
3962
    public function has_audio()
3963
    {
3964
        $has = false;
3965
        foreach ($this->items as $i => $item) {
3966
            if (!empty($this->items[$i]->audio)) {
3967
                $has = true;
3968
                break;
3969
            }
3970
        }
3971
3972
        return $has;
3973
    }
3974
3975
    /**
3976
     * Moves an item up and down at its level.
3977
     *
3978
     * @param int    $id        Item to move up and down
3979
     * @param string $direction Direction 'up' or 'down'
3980
     *
3981
     * @return bool|int
3982
     */
3983
    public function move_item($id, $direction)
3984
    {
3985
        $course_id = api_get_course_int_id();
3986
        if (empty($id) || empty($direction)) {
3987
            return false;
3988
        }
3989
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3990
        $sql_sel = "SELECT *
3991
                    FROM $tbl_lp_item
3992
                    WHERE
3993
                        iid = $id
3994
                    ";
3995
        $res_sel = Database::query($sql_sel);
3996
        // Check if elem exists.
3997
        if (Database::num_rows($res_sel) < 1) {
3998
            return false;
3999
        }
4000
        // Gather data.
4001
        $row = Database::fetch_array($res_sel);
4002
        $previous = $row['previous_item_id'];
4003
        $next = $row['next_item_id'];
4004
        $display = $row['display_order'];
4005
        $parent = $row['parent_item_id'];
4006
        $lp = $row['lp_id'];
4007
        // Update the item (switch with previous/next one).
4008
        switch ($direction) {
4009
            case 'up':
4010
                if ($display > 1) {
4011
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4012
                                 WHERE iid = $previous";
4013
                    $res_sel2 = Database::query($sql_sel2);
4014
                    if (Database::num_rows($res_sel2) < 1) {
4015
                        $previous_previous = 0;
4016
                    }
4017
                    // Gather data.
4018
                    $row2 = Database::fetch_array($res_sel2);
4019
                    $previous_previous = $row2['previous_item_id'];
4020
                    // Update previous_previous item (switch "next" with current).
4021
                    if ($previous_previous != 0) {
4022
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4023
                                        next_item_id = $id
4024
                                    WHERE iid = $previous_previous";
4025
                        Database::query($sql_upd2);
4026
                    }
4027
                    // Update previous item (switch with current).
4028
                    if ($previous != 0) {
4029
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4030
                                    next_item_id = $next,
4031
                                    previous_item_id = $id,
4032
                                    display_order = display_order +1
4033
                                    WHERE iid = $previous";
4034
                        Database::query($sql_upd2);
4035
                    }
4036
4037
                    // Update current item (switch with previous).
4038
                    if ($id != 0) {
4039
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4040
                                        next_item_id = $previous,
4041
                                        previous_item_id = $previous_previous,
4042
                                        display_order = display_order-1
4043
                                    WHERE c_id = ".$course_id." AND id = $id";
4044
                        Database::query($sql_upd2);
4045
                    }
4046
                    // Update next item (new previous item).
4047
                    if (!empty($next)) {
4048
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
4049
                                     WHERE iid = $next";
4050
                        Database::query($sql_upd2);
4051
                    }
4052
                    $display = $display - 1;
4053
                }
4054
                break;
4055
            case 'down':
4056
                if ($next != 0) {
4057
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4058
                                 WHERE iid = $next";
4059
                    $res_sel2 = Database::query($sql_sel2);
4060
                    if (Database::num_rows($res_sel2) < 1) {
4061
                        $next_next = 0;
4062
                    }
4063
                    // Gather data.
4064
                    $row2 = Database::fetch_array($res_sel2);
4065
                    $next_next = $row2['next_item_id'];
4066
                    // Update previous item (switch with current).
4067
                    if ($previous != 0) {
4068
                        $sql_upd2 = "UPDATE $tbl_lp_item
4069
                                     SET next_item_id = $next
4070
                                     WHERE iid = $previous";
4071
                        Database::query($sql_upd2);
4072
                    }
4073
                    // Update current item (switch with previous).
4074
                    if ($id != 0) {
4075
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4076
                                     previous_item_id = $next,
4077
                                     next_item_id = $next_next,
4078
                                     display_order = display_order + 1
4079
                                     WHERE iid = $id";
4080
                        Database::query($sql_upd2);
4081
                    }
4082
4083
                    // Update next item (new previous item).
4084
                    if ($next != 0) {
4085
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4086
                                     previous_item_id = $previous,
4087
                                     next_item_id = $id,
4088
                                     display_order = display_order-1
4089
                                     WHERE iid = $next";
4090
                        Database::query($sql_upd2);
4091
                    }
4092
4093
                    // Update next_next item (switch "previous" with current).
4094
                    if ($next_next != 0) {
4095
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4096
                                     previous_item_id = $id
4097
                                     WHERE iid = $next_next";
4098
                        Database::query($sql_upd2);
4099
                    }
4100
                    $display = $display + 1;
4101
                }
4102
                break;
4103
            default:
4104
                return false;
4105
        }
4106
4107
        return $display;
4108
    }
4109
4110
    /**
4111
     * Move a LP up (display_order).
4112
     *
4113
     * @param int $lp_id      Learnpath ID
4114
     * @param int $categoryId Category ID
4115
     *
4116
     * @return bool
4117
     */
4118
    public static function move_up($lp_id, $categoryId = 0)
4119
    {
4120
        $courseId = api_get_course_int_id();
4121
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4122
4123
        $categoryCondition = '';
4124
        if (!empty($categoryId)) {
4125
            $categoryId = (int) $categoryId;
4126
            $categoryCondition = " AND category_id = $categoryId";
4127
        }
4128
        $sql = "SELECT * FROM $lp_table
4129
                WHERE c_id = $courseId
4130
                $categoryCondition
4131
                ORDER BY display_order";
4132
        $res = Database::query($sql);
4133
        if ($res === false) {
4134
            return false;
4135
        }
4136
4137
        $lps = [];
4138
        $lp_order = [];
4139
        $num = Database::num_rows($res);
4140
        // First check the order is correct, globally (might be wrong because
4141
        // of versions < 1.8.4)
4142
        if ($num > 0) {
4143
            $i = 1;
4144
            while ($row = Database::fetch_array($res)) {
4145
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
4146
                    $sql = "UPDATE $lp_table SET display_order = $i
4147
                            WHERE iid = ".$row['iid'];
4148
                    Database::query($sql);
4149
                }
4150
                $row['display_order'] = $i;
4151
                $lps[$row['iid']] = $row;
4152
                $lp_order[$i] = $row['iid'];
4153
                $i++;
4154
            }
4155
        }
4156
        if ($num > 1) { // If there's only one element, no need to sort.
4157
            $order = $lps[$lp_id]['display_order'];
4158
            if ($order > 1) { // If it's the first element, no need to move up.
4159
                $sql = "UPDATE $lp_table SET display_order = $order
4160
                        WHERE iid = ".$lp_order[$order - 1];
4161
                Database::query($sql);
4162
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
4163
                        WHERE iid = $lp_id";
4164
                Database::query($sql);
4165
            }
4166
        }
4167
4168
        return true;
4169
    }
4170
4171
    /**
4172
     * Move a learnpath down (display_order).
4173
     *
4174
     * @param int $lp_id      Learnpath ID
4175
     * @param int $categoryId Category ID
4176
     *
4177
     * @return bool
4178
     */
4179
    public static function move_down($lp_id, $categoryId = 0)
4180
    {
4181
        $courseId = api_get_course_int_id();
4182
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4183
4184
        $categoryCondition = '';
4185
        if (!empty($categoryId)) {
4186
            $categoryId = (int) $categoryId;
4187
            $categoryCondition = " AND category_id = $categoryId";
4188
        }
4189
4190
        $sql = "SELECT * FROM $lp_table
4191
                WHERE c_id = $courseId
4192
                $categoryCondition
4193
                ORDER BY display_order";
4194
        $res = Database::query($sql);
4195
        if ($res === false) {
4196
            return false;
4197
        }
4198
        $lps = [];
4199
        $lp_order = [];
4200
        $num = Database::num_rows($res);
4201
        $max = 0;
4202
        // First check the order is correct, globally (might be wrong because
4203
        // of versions < 1.8.4).
4204
        if ($num > 0) {
4205
            $i = 1;
4206
            while ($row = Database::fetch_array($res)) {
4207
                $max = $i;
4208
                if ($row['display_order'] != $i) {
4209
                    // If we find a gap in the order, we need to fix it.
4210
                    $sql = "UPDATE $lp_table SET display_order = $i
4211
                              WHERE iid = ".$row['iid'];
4212
                    Database::query($sql);
4213
                }
4214
                $row['display_order'] = $i;
4215
                $lps[$row['iid']] = $row;
4216
                $lp_order[$i] = $row['iid'];
4217
                $i++;
4218
            }
4219
        }
4220
        if ($num > 1) { // If there's only one element, no need to sort.
4221
            $order = $lps[$lp_id]['display_order'];
4222
            if ($order < $max) { // If it's the first element, no need to move up.
4223
                $sql = "UPDATE $lp_table SET display_order = $order
4224
                        WHERE iid = ".$lp_order[$order + 1];
4225
                Database::query($sql);
4226
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4227
                        WHERE iid = $lp_id";
4228
                Database::query($sql);
4229
            }
4230
        }
4231
4232
        return true;
4233
    }
4234
4235
    /**
4236
     * Updates learnpath attributes to point to the next element
4237
     * The last part is similar to set_current_item but processing the other way around.
4238
     */
4239
    public function next()
4240
    {
4241
        if ($this->debug > 0) {
4242
            error_log('In learnpath::next()', 0);
4243
        }
4244
        $this->last = $this->get_current_item_id();
4245
        $this->items[$this->last]->save(
4246
            false,
4247
            $this->prerequisites_match($this->last)
4248
        );
4249
        $this->autocomplete_parents($this->last);
4250
        $new_index = $this->get_next_index();
4251
        if ($this->debug > 2) {
4252
            error_log('New index: '.$new_index, 0);
4253
        }
4254
        $this->index = $new_index;
4255
        if ($this->debug > 2) {
4256
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4257
        }
4258
        $this->current = $this->ordered_items[$new_index];
4259
        if ($this->debug > 2) {
4260
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4261
        }
4262
    }
4263
4264
    /**
4265
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4266
     * class, this might be redefined to allow several behaviours depending on the document type.
4267
     *
4268
     * @param int $id Resource ID
4269
     */
4270
    public function open($id)
4271
    {
4272
        // TODO:
4273
        // set the current resource attribute to this resource
4274
        // switch on element type (redefine in child class?)
4275
        // set status for this item to "opened"
4276
        // start timer
4277
        // initialise score
4278
        $this->index = 0; //or = the last item seen (see $this->last)
4279
    }
4280
4281
    /**
4282
     * Check that all prerequisites are fulfilled. Returns true and an
4283
     * empty string on success, returns false
4284
     * and the prerequisite string on error.
4285
     * This function is based on the rules for aicc_script language as
4286
     * described in the SCORM 1.2 CAM documentation page 108.
4287
     *
4288
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4289
     *
4290
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4291
     *              string otherwise
4292
     */
4293
    public function prerequisites_match($itemId = null)
4294
    {
4295
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4296
        if ($allow) {
4297
            if (api_is_allowed_to_edit() ||
4298
                api_is_platform_admin(true) ||
4299
                api_is_drh() ||
4300
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4301
            ) {
4302
                return true;
4303
            }
4304
        }
4305
4306
        $debug = $this->debug;
4307
        if ($debug > 0) {
4308
            error_log('In learnpath::prerequisites_match()');
4309
        }
4310
4311
        if (empty($itemId)) {
4312
            $itemId = $this->current;
4313
        }
4314
4315
        $currentItem = $this->getItem($itemId);
4316
        if ($debug > 0) {
4317
            error_log("Checking item id $itemId");
4318
        }
4319
4320
        if ($currentItem) {
4321
            if ($this->type == 2) {
4322
                // Getting prereq from scorm
4323
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4324
            } else {
4325
                $prereq_string = $currentItem->get_prereq_string();
4326
            }
4327
4328
            if (empty($prereq_string)) {
4329
                if ($debug > 0) {
4330
                    error_log('Found prereq_string is empty return true');
4331
                }
4332
4333
                return true;
4334
            }
4335
4336
            // Clean spaces.
4337
            $prereq_string = str_replace(' ', '', $prereq_string);
4338
            if ($debug > 0) {
4339
                error_log('Found prereq_string: '.$prereq_string, 0);
4340
            }
4341
4342
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4343
            $result = $currentItem->parse_prereq(
4344
                $prereq_string,
4345
                $this->items,
4346
                $this->refs_list,
4347
                $this->get_user_id()
4348
            );
4349
4350
            if ($result === false) {
4351
                $this->set_error_msg($currentItem->prereq_alert);
4352
            }
4353
        } else {
4354
            $result = true;
4355
            if ($debug > 1) {
4356
                error_log('$this->items['.$itemId.'] was not an object');
4357
            }
4358
        }
4359
4360
        if ($debug > 1) {
4361
            error_log('Result: '.$result);
4362
            error_log('End of prerequisites_match(). Error message is now '.$this->error);
4363
        }
4364
4365
        return $result;
4366
    }
4367
4368
    /**
4369
     * Updates learnpath attributes to point to the previous element
4370
     * The last part is similar to set_current_item but processing the other way around.
4371
     */
4372
    public function previous()
4373
    {
4374
        $this->last = $this->get_current_item_id();
4375
        $this->items[$this->last]->save(
4376
            false,
4377
            $this->prerequisites_match($this->last)
4378
        );
4379
        $this->autocomplete_parents($this->last);
4380
        $new_index = $this->get_previous_index();
4381
        $this->index = $new_index;
4382
        $this->current = $this->ordered_items[$new_index];
4383
    }
4384
4385
    /**
4386
     * Publishes a learnpath. This basically means show or hide the learnpath
4387
     * to normal users.
4388
     * Can be used as abstract.
4389
     *
4390
     * @param int $lp_id          Learnpath ID
4391
     * @param int $set_visibility New visibility
4392
     *
4393
     * @return bool
4394
     */
4395
    public static function toggle_visibility($lp_id, $set_visibility = 1)
4396
    {
4397
        $action = 'visible';
4398
        if ($set_visibility != 1) {
4399
            $action = 'invisible';
4400
            self::toggle_publish($lp_id, 'i');
4401
        }
4402
4403
        return api_item_property_update(
4404
            api_get_course_info(),
4405
            TOOL_LEARNPATH,
4406
            $lp_id,
4407
            $action,
4408
            api_get_user_id()
4409
        );
4410
    }
4411
4412
    /**
4413
     * Publishes a learnpath category.
4414
     * This basically means show or hide the learnpath category to normal users.
4415
     *
4416
     * @param int $id
4417
     * @param int $visibility
4418
     *
4419
     * @throws \Doctrine\ORM\NonUniqueResultException
4420
     * @throws \Doctrine\ORM\ORMException
4421
     * @throws \Doctrine\ORM\OptimisticLockException
4422
     * @throws \Doctrine\ORM\TransactionRequiredException
4423
     *
4424
     * @return bool
4425
     */
4426
    public static function toggleCategoryVisibility($id, $visibility = 1)
4427
    {
4428
        $action = 'visible';
4429
        if ($visibility != 1) {
4430
            self::toggleCategoryPublish($id, 0);
4431
            $action = 'invisible';
4432
        }
4433
4434
        return api_item_property_update(
4435
            api_get_course_info(),
4436
            TOOL_LEARNPATH_CATEGORY,
4437
            $id,
4438
            $action,
4439
            api_get_user_id()
4440
        );
4441
    }
4442
4443
    /**
4444
     * Publishes a learnpath. This basically means show or hide the learnpath
4445
     * on the course homepage
4446
     * Can be used as abstract.
4447
     *
4448
     * @param int    $lp_id          Learnpath id
4449
     * @param string $set_visibility New visibility (v/i - visible/invisible)
4450
     *
4451
     * @return bool
4452
     */
4453
    public static function toggle_publish($lp_id, $set_visibility = 'v')
4454
    {
4455
        $course_id = api_get_course_int_id();
4456
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4457
        $lp_id = (int) $lp_id;
4458
        $sql = "SELECT * FROM $tbl_lp
4459
                WHERE iid = $lp_id";
4460
        $result = Database::query($sql);
4461
        if (Database::num_rows($result)) {
4462
            $row = Database::fetch_array($result);
4463
            $name = Database::escape_string($row['name']);
4464
            if ($set_visibility == 'i') {
4465
                $v = 0;
4466
            }
4467
            if ($set_visibility == 'v') {
4468
                $v = 1;
4469
            }
4470
4471
            $session_id = api_get_session_id();
4472
            $session_condition = api_get_session_condition($session_id);
4473
4474
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4475
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4476
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4477
4478
            $sql = "SELECT * FROM $tbl_tool
4479
                    WHERE
4480
                        c_id = $course_id AND
4481
                        (link = '$link' OR link = '$oldLink') AND
4482
                        image = 'scormbuilder.gif' AND
4483
                        (
4484
                            link LIKE '$link%' OR
4485
                            link LIKE '$oldLink%'
4486
                        )
4487
                        $session_condition
4488
                    ";
4489
4490
            $result = Database::query($sql);
4491
            $num = Database::num_rows($result);
4492
            if ($set_visibility == 'i' && $num > 0) {
4493
                $sql = "DELETE FROM $tbl_tool
4494
                        WHERE
4495
                            c_id = $course_id AND
4496
                            (link = '$link' OR link = '$oldLink') AND
4497
                            image='scormbuilder.gif'
4498
                            $session_condition";
4499
                Database::query($sql);
4500
            } elseif ($set_visibility == 'v' && $num == 0) {
4501
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4502
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4503
                Database::query($sql);
4504
                $insertId = Database::insert_id();
4505
                if ($insertId) {
4506
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4507
                    Database::query($sql);
4508
                }
4509
            } elseif ($set_visibility == 'v' && $num > 0) {
4510
                $sql = "UPDATE $tbl_tool SET
4511
                            c_id = $course_id,
4512
                            name = '$name',
4513
                            link = '$link',
4514
                            image = 'scormbuilder.gif',
4515
                            visibility = '$v',
4516
                            admin = '0',
4517
                            address = 'pastillegris.gif',
4518
                            added_tool = 0,
4519
                            session_id = $session_id
4520
                        WHERE
4521
                            c_id = ".$course_id." AND
4522
                            (link = '$link' OR link = '$oldLink') AND
4523
                            image='scormbuilder.gif'
4524
                            $session_condition
4525
                        ";
4526
                Database::query($sql);
4527
            }
4528
        }
4529
4530
        return false;
4531
    }
4532
4533
    /**
4534
     * Publishes a learnpath.
4535
     * Show or hide the learnpath category on the course homepage.
4536
     *
4537
     * @param int $id
4538
     * @param int $setVisibility
4539
     *
4540
     * @throws \Doctrine\ORM\NonUniqueResultException
4541
     * @throws \Doctrine\ORM\ORMException
4542
     * @throws \Doctrine\ORM\OptimisticLockException
4543
     * @throws \Doctrine\ORM\TransactionRequiredException
4544
     *
4545
     * @return bool
4546
     */
4547
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4548
    {
4549
        $courseId = api_get_course_int_id();
4550
        $sessionId = api_get_session_id();
4551
        $sessionCondition = api_get_session_condition(
4552
            $sessionId,
4553
            true,
4554
            false,
4555
            't.sessionId'
4556
        );
4557
4558
        $em = Database::getManager();
4559
        $category = self::getCategory($id);
4560
4561
        if (!$category) {
4562
            return false;
4563
        }
4564
4565
        if (empty($courseId)) {
4566
            return false;
4567
        }
4568
4569
        $link = self::getCategoryLinkForTool($id);
4570
4571
        /** @var CTool $tool */
4572
        $tool = $em->createQuery("
4573
                SELECT t FROM ChamiloCourseBundle:CTool t
4574
                WHERE
4575
                    t.cId = :course AND
4576
                    t.link = :link1 AND
4577
                    t.image = 'lp_category.gif' AND
4578
                    t.link LIKE :link2
4579
                    $sessionCondition
4580
            ")
4581
            ->setParameters([
4582
                'course' => $courseId,
4583
                'link1' => $link,
4584
                'link2' => "$link%",
4585
            ])
4586
            ->getOneOrNullResult();
4587
4588
        if ($setVisibility == 0 && $tool) {
4589
            $em->remove($tool);
4590
            $em->flush();
4591
4592
            return true;
4593
        }
4594
4595
        if ($setVisibility == 1 && !$tool) {
4596
            $tool = new CTool();
4597
            $tool
4598
                ->setCategory('authoring')
4599
                ->setCId($courseId)
4600
                ->setName(strip_tags($category->getName()))
4601
                ->setLink($link)
4602
                ->setImage('lp_category.gif')
4603
                ->setVisibility(1)
4604
                ->setAdmin(0)
4605
                ->setAddress('pastillegris.gif')
4606
                ->setAddedTool(0)
4607
                ->setSessionId($sessionId)
4608
                ->setTarget('_self');
4609
4610
            $em->persist($tool);
4611
            $em->flush();
4612
4613
            $tool->setId($tool->getIid());
4614
4615
            $em->persist($tool);
4616
            $em->flush();
4617
4618
            return true;
4619
        }
4620
4621
        if ($setVisibility == 1 && $tool) {
4622
            $tool
4623
                ->setName(strip_tags($category->getName()))
4624
                ->setVisibility(1);
4625
4626
            $em->persist($tool);
4627
            $em->flush();
4628
4629
            return true;
4630
        }
4631
4632
        return false;
4633
    }
4634
4635
    /**
4636
     * Check if the learnpath category is visible for a user.
4637
     *
4638
     * @param int
4639
     * @param int
4640
     *
4641
     * @return bool
4642
     */
4643
    public static function categoryIsVisibleForStudent(
4644
        CLpCategory $category,
4645
        User $user,
4646
        $courseId = 0,
4647
        $sessionId = 0
4648
    ) {
4649
        if (empty($category)) {
4650
            return false;
4651
        }
4652
4653
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4654
4655
        if ($isAllowedToEdit) {
4656
            return true;
4657
        }
4658
4659
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4660
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4661
4662
        $courseInfo = api_get_course_info_by_id($courseId);
4663
4664
        $categoryVisibility = api_get_item_visibility(
4665
            $courseInfo,
4666
            TOOL_LEARNPATH_CATEGORY,
4667
            $category->getId(),
4668
            $sessionId
4669
        );
4670
4671
        if ($categoryVisibility !== 1 && $categoryVisibility != -1) {
4672
            return false;
4673
        }
4674
4675
        $subscriptionSettings = self::getSubscriptionSettings();
4676
4677
        if ($subscriptionSettings['allow_add_users_to_lp_category'] == false) {
4678
            return true;
4679
        }
4680
4681
        $noUserSubscribed = false;
4682
        $noGroupSubscribed = true;
4683
        $users = $category->getUsers();
4684
        if (empty($users) || !$users->count()) {
4685
            $noUserSubscribed = true;
4686
        } elseif ($category->hasUserAdded($user)) {
4687
            return true;
4688
        }
4689
4690
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4691
        $em = Database::getManager();
4692
4693
        /** @var ItemPropertyRepository $itemRepo */
4694
        $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4695
4696
        /** @var CourseRepository $courseRepo */
4697
        $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4698
        $session = null;
4699
        if (!empty($sessionId)) {
4700
            $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4701
        }
4702
4703
        $course = $courseRepo->find($courseId);
4704
4705
        if ($courseId != 0) {
4706
            // Subscribed groups to a LP
4707
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4708
                TOOL_LEARNPATH_CATEGORY,
4709
                $category->getId(),
4710
                $course,
4711
                $session
4712
            );
4713
        }
4714
4715
        if (!empty($subscribedGroupsInLp)) {
4716
            $noGroupSubscribed = false;
4717
            if (!empty($groups)) {
4718
                $groups = array_column($groups, 'iid');
4719
                /** @var CItemProperty $item */
4720
                foreach ($subscribedGroupsInLp as $item) {
4721
                    if ($item->getGroup() &&
4722
                        in_array($item->getGroup()->getId(), $groups)
4723
                    ) {
4724
                        return true;
4725
                    }
4726
                }
4727
            }
4728
        }
4729
        $response = $noGroupSubscribed && $noUserSubscribed;
4730
4731
        return $response;
4732
    }
4733
4734
    /**
4735
     * Check if a learnpath category is published as course tool.
4736
     *
4737
     * @param int $courseId
4738
     *
4739
     * @return bool
4740
     */
4741
    public static function categoryIsPublished(CLpCategory $category, $courseId)
4742
    {
4743
        $link = self::getCategoryLinkForTool($category->getId());
4744
        $em = Database::getManager();
4745
4746
        $tools = $em
4747
            ->createQuery("
4748
                SELECT t FROM ChamiloCourseBundle:CTool t
4749
                WHERE t.cId = :course AND
4750
                    t.name = :name AND
4751
                    t.image = 'lp_category.gif' AND
4752
                    t.link LIKE :link
4753
            ")
4754
            ->setParameters([
4755
                'course' => $courseId,
4756
                'name' => strip_tags($category->getName()),
4757
                'link' => "$link%",
4758
            ])
4759
            ->getResult();
4760
4761
        /** @var CTool $tool */
4762
        $tool = current($tools);
4763
4764
        return $tool ? $tool->getVisibility() : false;
4765
    }
4766
4767
    /**
4768
     * Restart the whole learnpath. Return the URL of the first element.
4769
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4770
     * To use a similar method  statically, use the create_new_attempt() method.
4771
     *
4772
     * @return bool
4773
     */
4774
    public function restart()
4775
    {
4776
        if ($this->debug > 0) {
4777
            error_log('In learnpath::restart()', 0);
4778
        }
4779
        // TODO
4780
        // Call autosave method to save the current progress.
4781
        //$this->index = 0;
4782
        if (api_is_invitee()) {
4783
            return false;
4784
        }
4785
        $session_id = api_get_session_id();
4786
        $course_id = api_get_course_int_id();
4787
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4788
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4789
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4790
        if ($this->debug > 2) {
4791
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4792
        }
4793
        Database::query($sql);
4794
        $view_id = Database::insert_id();
4795
4796
        if ($view_id) {
4797
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4798
            Database::query($sql);
4799
            $this->lp_view_id = $view_id;
4800
            $this->attempt = $this->attempt + 1;
4801
        } else {
4802
            $this->error = 'Could not insert into item_view table...';
4803
4804
            return false;
4805
        }
4806
        $this->autocomplete_parents($this->current);
4807
        foreach ($this->items as $index => $dummy) {
4808
            $this->items[$index]->restart();
4809
            $this->items[$index]->set_lp_view($this->lp_view_id);
4810
        }
4811
        $this->first();
4812
4813
        return true;
4814
    }
4815
4816
    /**
4817
     * Saves the current item.
4818
     *
4819
     * @return bool
4820
     */
4821
    public function save_current()
4822
    {
4823
        $debug = $this->debug;
4824
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4825
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4826
        if ($debug) {
4827
            error_log('save_current() saving item '.$this->current, 0);
4828
            error_log(''.print_r($this->items, true), 0);
4829
        }
4830
        if (isset($this->items[$this->current]) &&
4831
            is_object($this->items[$this->current])
4832
        ) {
4833
            if ($debug) {
4834
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4835
            }
4836
4837
            $res = $this->items[$this->current]->save(
4838
                false,
4839
                $this->prerequisites_match($this->current)
4840
            );
4841
            $this->autocomplete_parents($this->current);
4842
            $status = $this->items[$this->current]->get_status();
4843
            $this->update_queue[$this->current] = $status;
4844
4845
            if ($debug) {
4846
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4847
            }
4848
4849
            return $res;
4850
        }
4851
4852
        return false;
4853
    }
4854
4855
    /**
4856
     * Saves the given item.
4857
     *
4858
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4859
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4860
     *
4861
     * @return bool
4862
     */
4863
    public function save_item($item_id = null, $from_outside = true)
4864
    {
4865
        $debug = $this->debug;
4866
        if ($debug) {
4867
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4868
        }
4869
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4870
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4871
        if (empty($item_id)) {
4872
            $item_id = (int) $_REQUEST['id'];
4873
        }
4874
4875
        if (empty($item_id)) {
4876
            $item_id = $this->get_current_item_id();
4877
        }
4878
        if (isset($this->items[$item_id]) &&
4879
            is_object($this->items[$item_id])
4880
        ) {
4881
            // Saving the item.
4882
            $res = $this->items[$item_id]->save(
4883
                $from_outside,
4884
                $this->prerequisites_match($item_id)
4885
            );
4886
4887
            if ($debug) {
4888
                error_log('update_queue before:');
4889
                error_log(print_r($this->update_queue, 1));
4890
            }
4891
            $this->autocomplete_parents($item_id);
4892
4893
            $status = $this->items[$item_id]->get_status();
4894
            $this->update_queue[$item_id] = $status;
4895
4896
            if ($debug) {
4897
                error_log('get_status(): '.$status);
4898
                error_log('update_queue after:');
4899
                error_log(print_r($this->update_queue, 1));
4900
            }
4901
4902
            return $res;
4903
        }
4904
4905
        return false;
4906
    }
4907
4908
    /**
4909
     * Saves the last item seen's ID only in case.
4910
     */
4911
    public function save_last($score = null)
4912
    {
4913
        $course_id = api_get_course_int_id();
4914
        $debug = $this->debug;
4915
        if ($debug) {
4916
            error_log('In learnpath::save_last()', 0);
4917
        }
4918
        $session_condition = api_get_session_condition(
4919
            api_get_session_id(),
4920
            true,
4921
            false
4922
        );
4923
        $table = Database::get_course_table(TABLE_LP_VIEW);
4924
4925
        $userId = $this->get_user_id();
4926
        if (empty($userId)) {
4927
            $userId = api_get_user_id();
4928
            if ($debug) {
4929
                error_log('$this->get_user_id() was empty, used api_get_user_id() instead in '.__FILE__.' line '.__LINE__);
4930
            }
4931
        }
4932
        if (isset($this->current) && !api_is_invitee()) {
4933
            if ($debug) {
4934
                error_log('Saving current item ('.$this->current.') for later review', 0);
4935
            }
4936
            $sql = "UPDATE $table SET
4937
                        last_item = ".$this->get_current_item_id()."
4938
                    WHERE
4939
                        c_id = $course_id AND
4940
                        lp_id = ".$this->get_id()." AND
4941
                        user_id = ".$userId." ".$session_condition;
4942
            if ($debug) {
4943
                error_log('Saving last item seen : '.$sql, 0);
4944
            }
4945
            Database::query($sql);
4946
        }
4947
4948
        if (!api_is_invitee()) {
4949
            // Save progress.
4950
            list($progress) = $this->get_progress_bar_text('%');
4951
            $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
4952
            $scoreAsProgress = $this->getUseScoreAsProgress();
4953
            if ($scoreAsProgress && $scoreAsProgressSetting && (null === $score || empty($score) || -1 == $score)) {
4954
                if ($debug) {
4955
                    error_log("Return false: Dont save score: $score");
4956
                    error_log("progress: $progress");
4957
                }
4958
4959
                return false;
4960
            }
4961
4962
            if ($scoreAsProgress && $scoreAsProgressSetting) {
4963
                $storedProgress = self::getProgress(
4964
                    $this->get_id(),
4965
                    $userId,
4966
                    $course_id,
4967
                    $this->get_lp_session_id()
4968
                );
4969
4970
                // Check if the stored progress is higher than the new value
4971
                if ($storedProgress >= $progress) {
4972
                    if ($debug) {
4973
                        error_log("Return false: New progress value is lower than stored value - Current value: $storedProgress - New value: $progress [lp ".$this->get_id()." - user ".$userId."]");
4974
                    }
4975
4976
                    return false;
4977
                }
4978
            }
4979
4980
            if ($progress >= 0 && $progress <= 100) {
4981
                // Check database.
4982
                $progress = (int) $progress;
4983
                $sql = "UPDATE $table SET
4984
                            progress = $progress
4985
                        WHERE
4986
                            c_id = $course_id AND
4987
                            lp_id = ".$this->get_id()." AND
4988
                            user_id = ".$userId." ".$session_condition;
4989
                // Ignore errors as some tables might not have the progress field just yet.
4990
                Database::query($sql);
4991
                if ($debug) {
4992
                    error_log($sql);
4993
                }
4994
                $this->progress_db = $progress;
4995
4996
                if (100 == $progress) {
4997
                    HookLearningPathEnd::create()
4998
                        ->setEventData(['lp_view_id' => $this->lp_view_id])
4999
                        ->hookLearningPathEnd();
5000
                }
5001
            }
5002
        }
5003
    }
5004
5005
    /**
5006
     * Sets the current item ID (checks if valid and authorized first).
5007
     *
5008
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
5009
     */
5010
    public function set_current_item($item_id = null)
5011
    {
5012
        $debug = $this->debug;
5013
        if ($debug) {
5014
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
5015
        }
5016
        if (empty($item_id)) {
5017
            if ($debug) {
5018
                error_log('No new current item given, ignore...', 0);
5019
            }
5020
            // Do nothing.
5021
        } else {
5022
            if ($debug) {
5023
                error_log('New current item given is '.$item_id.'...', 0);
5024
            }
5025
            if (is_numeric($item_id)) {
5026
                $item_id = (int) $item_id;
5027
                // TODO: Check in database here.
5028
                $this->last = $this->current;
5029
                $this->current = $item_id;
5030
                // TODO: Update $this->index as well.
5031
                foreach ($this->ordered_items as $index => $item) {
5032
                    if ($item == $this->current) {
5033
                        $this->index = $index;
5034
                        break;
5035
                    }
5036
                }
5037
                if ($debug) {
5038
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
5039
                }
5040
            } else {
5041
                if ($debug) {
5042
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
5043
                }
5044
            }
5045
        }
5046
    }
5047
5048
    /**
5049
     * Sets the encoding.
5050
     *
5051
     * @param string $enc New encoding
5052
     *
5053
     * @return bool
5054
     *
5055
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
5056
     */
5057
    public function set_encoding($enc = 'UTF-8')
5058
    {
5059
        $enc = api_refine_encoding_id($enc);
5060
        if (empty($enc)) {
5061
            $enc = api_get_system_encoding();
5062
        }
5063
        if (api_is_encoding_supported($enc)) {
5064
            $lp = $this->get_id();
5065
            if ($lp != 0) {
5066
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5067
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
5068
                        WHERE iid = ".$lp;
5069
                $res = Database::query($sql);
5070
5071
                return $res;
5072
            }
5073
        }
5074
5075
        return false;
5076
    }
5077
5078
    /**
5079
     * Sets the JS lib setting in the database directly.
5080
     * This is the JavaScript library file this lp needs to load on startup.
5081
     *
5082
     * @param string $lib Proximity setting
5083
     *
5084
     * @return bool True on update success. False otherwise.
5085
     */
5086
    public function set_jslib($lib = '')
5087
    {
5088
        $lp = $this->get_id();
5089
5090
        if ($lp != 0) {
5091
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5092
            $lib = Database::escape_string($lib);
5093
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
5094
                    WHERE iid = $lp";
5095
            $res = Database::query($sql);
5096
5097
            return $res;
5098
        }
5099
5100
        return false;
5101
    }
5102
5103
    /**
5104
     * Sets the name of the LP maker (publisher) (and save).
5105
     *
5106
     * @param string $name Optional string giving the new content_maker of this learnpath
5107
     *
5108
     * @return bool True
5109
     */
5110
    public function set_maker($name = '')
5111
    {
5112
        if (empty($name)) {
5113
            return false;
5114
        }
5115
        $this->maker = $name;
5116
        $table = Database::get_course_table(TABLE_LP_MAIN);
5117
        $lp_id = $this->get_id();
5118
        $sql = "UPDATE $table SET
5119
                content_maker = '".Database::escape_string($this->maker)."'
5120
                WHERE iid = $lp_id";
5121
        Database::query($sql);
5122
5123
        return true;
5124
    }
5125
5126
    /**
5127
     * Sets the name of the current learnpath (and save).
5128
     *
5129
     * @param string $name Optional string giving the new name of this learnpath
5130
     *
5131
     * @return bool True/False
5132
     */
5133
    public function set_name($name = null)
5134
    {
5135
        if (empty($name)) {
5136
            return false;
5137
        }
5138
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5139
        $name = Database::escape_string($name);
5140
5141
        $this->name = $name;
5142
5143
        $lp_id = $this->get_id();
5144
        $course_id = $this->course_info['real_id'];
5145
        $sql = "UPDATE $lp_table SET
5146
                name = '$name'
5147
                WHERE iid = $lp_id";
5148
        $result = Database::query($sql);
5149
        // If the lp is visible on the homepage, change his name there.
5150
        if (Database::affected_rows($result)) {
5151
            $session_id = api_get_session_id();
5152
            $session_condition = api_get_session_condition($session_id);
5153
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
5154
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
5155
            $sql = "UPDATE $tbl_tool SET name = '$name'
5156
            	    WHERE
5157
            	        c_id = $course_id AND
5158
            	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
5159
            Database::query($sql);
5160
5161
            return true;
5162
        }
5163
5164
        return false;
5165
    }
5166
5167
    /**
5168
     * Set index specified prefix terms for all items in this path.
5169
     *
5170
     * @param string $terms_string Comma-separated list of terms
5171
     * @param string $prefix       Xapian term prefix
5172
     *
5173
     * @return bool False on error, true otherwise
5174
     */
5175
    public function set_terms_by_prefix($terms_string, $prefix)
5176
    {
5177
        $course_id = api_get_course_int_id();
5178
        if (api_get_setting('search_enabled') !== 'true') {
5179
            return false;
5180
        }
5181
5182
        if (!extension_loaded('xapian')) {
5183
            return false;
5184
        }
5185
5186
        $terms_string = trim($terms_string);
5187
        $terms = explode(',', $terms_string);
5188
        array_walk($terms, 'trim_value');
5189
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5190
5191
        // Don't do anything if no change, verify only at DB, not the search engine.
5192
        if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
5193
            return false;
5194
        }
5195
5196
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5197
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5198
5199
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5200
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5201
        $lp_id = (int) $_POST['lp_id'];
5202
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5203
        $result = Database::query($sql);
5204
        $di = new ChamiloIndexer();
5205
5206
        while ($lp_item = Database::fetch_array($result)) {
5207
            // Get search_did.
5208
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5209
            $sql = 'SELECT * FROM %s
5210
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5211
                    LIMIT 1';
5212
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5213
5214
            //echo $sql; echo '<br>';
5215
            $res = Database::query($sql);
5216
            if (Database::num_rows($res) > 0) {
5217
                $se_ref = Database::fetch_array($res);
5218
                // Compare terms.
5219
                $doc = $di->get_document($se_ref['search_did']);
5220
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5221
                $xterms = [];
5222
                foreach ($xapian_terms as $xapian_term) {
5223
                    $xterms[] = substr($xapian_term['name'], 1);
5224
                }
5225
5226
                $dterms = $terms;
5227
                $missing_terms = array_diff($dterms, $xterms);
5228
                $deprecated_terms = array_diff($xterms, $dterms);
5229
5230
                // Save it to search engine.
5231
                foreach ($missing_terms as $term) {
5232
                    $doc->add_term($prefix.$term, 1);
5233
                }
5234
                foreach ($deprecated_terms as $term) {
5235
                    $doc->remove_term($prefix.$term);
5236
                }
5237
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5238
                $di->getDb()->flush();
5239
            }
5240
        }
5241
5242
        return true;
5243
    }
5244
5245
    /**
5246
     * Sets the theme of the LP (local/remote) (and save).
5247
     *
5248
     * @param string $name Optional string giving the new theme of this learnpath
5249
     *
5250
     * @return bool Returns true if theme name is not empty
5251
     */
5252
    public function set_theme($name = '')
5253
    {
5254
        $this->theme = $name;
5255
        $table = Database::get_course_table(TABLE_LP_MAIN);
5256
        $lp_id = $this->get_id();
5257
        $sql = "UPDATE $table
5258
                SET theme = '".Database::escape_string($this->theme)."'
5259
                WHERE iid = $lp_id";
5260
        Database::query($sql);
5261
5262
        return true;
5263
    }
5264
5265
    /**
5266
     * Sets the image of an LP (and save).
5267
     *
5268
     * @param string $name Optional string giving the new image of this learnpath
5269
     *
5270
     * @return bool Returns true if theme name is not empty
5271
     */
5272
    public function set_preview_image($name = '')
5273
    {
5274
        $this->preview_image = $name;
5275
        $table = Database::get_course_table(TABLE_LP_MAIN);
5276
        $lp_id = $this->get_id();
5277
        $sql = "UPDATE $table SET
5278
                preview_image = '".Database::escape_string($this->preview_image)."'
5279
                WHERE iid = $lp_id";
5280
        Database::query($sql);
5281
5282
        return true;
5283
    }
5284
5285
    /**
5286
     * Sets the author of a LP (and save).
5287
     *
5288
     * @param string $name Optional string giving the new author of this learnpath
5289
     *
5290
     * @return bool Returns true if author's name is not empty
5291
     */
5292
    public function set_author($name = '')
5293
    {
5294
        $this->author = $name;
5295
        $table = Database::get_course_table(TABLE_LP_MAIN);
5296
        $lp_id = $this->get_id();
5297
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5298
                WHERE iid = $lp_id";
5299
        Database::query($sql);
5300
5301
        return true;
5302
    }
5303
5304
    /**
5305
     * Sets the hide_toc_frame parameter of a LP (and save).
5306
     *
5307
     * @param int $hide 1 if frame is hidden 0 then else
5308
     *
5309
     * @return bool Returns true if author's name is not empty
5310
     */
5311
    public function set_hide_toc_frame($hide)
5312
    {
5313
        if (intval($hide) == $hide) {
5314
            $this->hide_toc_frame = $hide;
5315
            $table = Database::get_course_table(TABLE_LP_MAIN);
5316
            $lp_id = $this->get_id();
5317
            $sql = "UPDATE $table SET
5318
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5319
                    WHERE iid = $lp_id";
5320
            Database::query($sql);
5321
5322
            return true;
5323
        }
5324
5325
        return false;
5326
    }
5327
5328
    /**
5329
     * Sets the prerequisite of a LP (and save).
5330
     *
5331
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5332
     *
5333
     * @return bool returns true if prerequisite is not empty
5334
     */
5335
    public function set_prerequisite($prerequisite)
5336
    {
5337
        $this->prerequisite = (int) $prerequisite;
5338
        $table = Database::get_course_table(TABLE_LP_MAIN);
5339
        $lp_id = $this->get_id();
5340
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5341
                WHERE iid = $lp_id";
5342
        Database::query($sql);
5343
5344
        return true;
5345
    }
5346
5347
    /**
5348
     * Sets the location/proximity of the LP (local/remote) (and save).
5349
     *
5350
     * @param string $name Optional string giving the new location of this learnpath
5351
     *
5352
     * @return bool True on success / False on error
5353
     */
5354
    public function set_proximity($name = '')
5355
    {
5356
        if (empty($name)) {
5357
            return false;
5358
        }
5359
5360
        $this->proximity = $name;
5361
        $table = Database::get_course_table(TABLE_LP_MAIN);
5362
        $lp_id = $this->get_id();
5363
        $sql = "UPDATE $table SET
5364
                    content_local = '".Database::escape_string($name)."'
5365
                WHERE iid = $lp_id";
5366
        Database::query($sql);
5367
5368
        return true;
5369
    }
5370
5371
    /**
5372
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5373
     *
5374
     * @param int $id DB ID of the item
5375
     */
5376
    public function set_previous_item($id)
5377
    {
5378
        if ($this->debug > 0) {
5379
            error_log('In learnpath::set_previous_item()', 0);
5380
        }
5381
        $this->last = $id;
5382
    }
5383
5384
    /**
5385
     * Sets use_max_score.
5386
     *
5387
     * @param int $use_max_score Optional string giving the new location of this learnpath
5388
     *
5389
     * @return bool True on success / False on error
5390
     */
5391
    public function set_use_max_score($use_max_score = 1)
5392
    {
5393
        $use_max_score = (int) $use_max_score;
5394
        $this->use_max_score = $use_max_score;
5395
        $table = Database::get_course_table(TABLE_LP_MAIN);
5396
        $lp_id = $this->get_id();
5397
        $sql = "UPDATE $table SET
5398
                    use_max_score = '".$this->use_max_score."'
5399
                WHERE iid = $lp_id";
5400
        Database::query($sql);
5401
5402
        return true;
5403
    }
5404
5405
    /**
5406
     * Sets and saves the expired_on date.
5407
     *
5408
     * @param string $expired_on Optional string giving the new author of this learnpath
5409
     *
5410
     * @throws \Doctrine\ORM\OptimisticLockException
5411
     *
5412
     * @return bool Returns true if author's name is not empty
5413
     */
5414
    public function set_expired_on($expired_on)
5415
    {
5416
        $em = Database::getManager();
5417
        /** @var CLp $lp */
5418
        $lp = $em
5419
            ->getRepository('ChamiloCourseBundle:CLp')
5420
            ->findOneBy(
5421
                [
5422
                    'iid' => $this->get_id(),
5423
                ]
5424
            );
5425
5426
        if (!$lp) {
5427
            return false;
5428
        }
5429
5430
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5431
5432
        $lp->setExpiredOn($this->expired_on);
5433
        $em->persist($lp);
5434
        $em->flush();
5435
5436
        return true;
5437
    }
5438
5439
    /**
5440
     * Sets and saves the publicated_on date.
5441
     *
5442
     * @param string $publicated_on Optional string giving the new author of this learnpath
5443
     *
5444
     * @throws \Doctrine\ORM\OptimisticLockException
5445
     *
5446
     * @return bool Returns true if author's name is not empty
5447
     */
5448
    public function set_publicated_on($publicated_on)
5449
    {
5450
        $em = Database::getManager();
5451
        /** @var CLp $lp */
5452
        $lp = $em
5453
            ->getRepository('ChamiloCourseBundle:CLp')
5454
            ->findOneBy(
5455
                [
5456
                    'iid' => $this->get_id(),
5457
                ]
5458
            );
5459
5460
        if (!$lp) {
5461
            return false;
5462
        }
5463
5464
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5465
        $lp->setPublicatedOn($this->publicated_on);
5466
        $em->persist($lp);
5467
        $em->flush();
5468
5469
        return true;
5470
    }
5471
5472
    /**
5473
     * Sets and saves the expired_on date.
5474
     *
5475
     * @return bool Returns true if author's name is not empty
5476
     */
5477
    public function set_modified_on()
5478
    {
5479
        $this->modified_on = api_get_utc_datetime();
5480
        $table = Database::get_course_table(TABLE_LP_MAIN);
5481
        $lp_id = $this->get_id();
5482
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5483
                WHERE iid = $lp_id";
5484
        Database::query($sql);
5485
5486
        return true;
5487
    }
5488
5489
    /**
5490
     * Sets the object's error message.
5491
     *
5492
     * @param string $error Error message. If empty, reinits the error string
5493
     */
5494
    public function set_error_msg($error = '')
5495
    {
5496
        if ($this->debug > 0) {
5497
            error_log('In learnpath::set_error_msg()', 0);
5498
        }
5499
        if (empty($error)) {
5500
            $this->error = '';
5501
        } else {
5502
            $this->error .= $error;
5503
        }
5504
    }
5505
5506
    /**
5507
     * Launches the current item if not 'sco'
5508
     * (starts timer and make sure there is a record ready in the DB).
5509
     *
5510
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5511
     *
5512
     * @return bool
5513
     */
5514
    public function start_current_item($allow_new_attempt = false)
5515
    {
5516
        $debug = $this->debug;
5517
        if ($debug) {
5518
            error_log('In learnpath::start_current_item()');
5519
            error_log('current: '.$this->current);
5520
        }
5521
        if ($this->current != 0 && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5522
            $type = $this->get_type();
5523
            $item_type = $this->items[$this->current]->get_type();
5524
            if (($type == 2 && $item_type != 'sco') ||
5525
                ($type == 3 && $item_type != 'au') ||
5526
                (
5527
                    $type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES &&
5528
                    WhispeakAuthPlugin::isAllowedToSaveLpItem($this->current)
5529
                )
5530
            ) {
5531
                if ($debug) {
5532
                    error_log('item type: '.$item_type);
5533
                    error_log('lp type: '.$type);
5534
                }
5535
                $this->items[$this->current]->open($allow_new_attempt);
5536
                $this->autocomplete_parents($this->current);
5537
                $prereq_check = $this->prerequisites_match($this->current);
5538
                if ($debug) {
5539
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5540
                }
5541
                $this->items[$this->current]->save(false, $prereq_check);
5542
            }
5543
            // If sco, then it is supposed to have been updated by some other call.
5544
            if ($item_type == 'sco') {
5545
                $this->items[$this->current]->restart();
5546
            }
5547
        }
5548
        if ($debug) {
5549
            error_log('lp_view_session_id: '.$this->lp_view_session_id);
5550
            error_log('api_get_session_id: '.api_get_session_id());
5551
            error_log('End of learnpath::start_current_item()');
5552
        }
5553
5554
        return true;
5555
    }
5556
5557
    /**
5558
     * Stops the processing and counters for the old item (as held in $this->last).
5559
     *
5560
     * @return bool True/False
5561
     */
5562
    public function stop_previous_item()
5563
    {
5564
        $debug = $this->debug;
5565
        if ($debug) {
5566
            error_log('In learnpath::stop_previous_item()');
5567
        }
5568
5569
        if ($this->last != 0 && $this->last != $this->current &&
5570
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5571
        ) {
5572
            if ($debug) {
5573
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5574
            }
5575
            switch ($this->get_type()) {
5576
                case '3':
5577
                    if ($this->items[$this->last]->get_type() != 'au') {
5578
                        if ($debug) {
5579
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5580
                        }
5581
                        $this->items[$this->last]->close();
5582
                    } else {
5583
                        if ($debug) {
5584
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5585
                        }
5586
                    }
5587
                    break;
5588
                case '2':
5589
                    if ($this->items[$this->last]->get_type() != 'sco') {
5590
                        if ($debug) {
5591
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5592
                        }
5593
                        $this->items[$this->last]->close();
5594
                    } else {
5595
                        if ($debug) {
5596
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5597
                        }
5598
                    }
5599
                    break;
5600
                case '1':
5601
                default:
5602
                    if ($debug) {
5603
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5604
                    }
5605
                    $this->items[$this->last]->close();
5606
                    break;
5607
            }
5608
        } else {
5609
            if ($debug) {
5610
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5611
            }
5612
5613
            return false;
5614
        }
5615
5616
        return true;
5617
    }
5618
5619
    /**
5620
     * Updates the default view mode from fullscreen to embedded and inversely.
5621
     *
5622
     * @return string The current default view mode ('fullscreen' or 'embedded')
5623
     */
5624
    public function update_default_view_mode()
5625
    {
5626
        $table = Database::get_course_table(TABLE_LP_MAIN);
5627
        $sql = "SELECT * FROM $table
5628
                WHERE iid = ".$this->get_id();
5629
        $res = Database::query($sql);
5630
        if (Database::num_rows($res) > 0) {
5631
            $row = Database::fetch_array($res);
5632
            $default_view_mode = $row['default_view_mod'];
5633
            $view_mode = $default_view_mode;
5634
            switch ($default_view_mode) {
5635
                case 'fullscreen': // default with popup
5636
                    $view_mode = 'embedded';
5637
                    break;
5638
                case 'embedded': // default view with left menu
5639
                    $view_mode = 'embedframe';
5640
                    break;
5641
                case 'embedframe': //folded menu
5642
                    $view_mode = 'impress';
5643
                    break;
5644
                case 'impress':
5645
                    $view_mode = 'fullscreen';
5646
                    break;
5647
            }
5648
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5649
                    WHERE iid = ".$this->get_id();
5650
            Database::query($sql);
5651
            $this->mode = $view_mode;
5652
5653
            return $view_mode;
5654
        }
5655
5656
        return -1;
5657
    }
5658
5659
    /**
5660
     * Updates the default behaviour about auto-commiting SCORM updates.
5661
     *
5662
     * @return bool True if auto-commit has been set to 'on', false otherwise
5663
     */
5664
    public function update_default_scorm_commit()
5665
    {
5666
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5667
        $sql = "SELECT * FROM $lp_table
5668
                WHERE iid = ".$this->get_id();
5669
        $res = Database::query($sql);
5670
        if (Database::num_rows($res) > 0) {
5671
            $row = Database::fetch_array($res);
5672
            $force = $row['force_commit'];
5673
            if ($force == 1) {
5674
                $force = 0;
5675
                $force_return = false;
5676
            } elseif ($force == 0) {
5677
                $force = 1;
5678
                $force_return = true;
5679
            }
5680
            $sql = "UPDATE $lp_table SET force_commit = $force
5681
                    WHERE iid = ".$this->get_id();
5682
            Database::query($sql);
5683
            $this->force_commit = $force_return;
5684
5685
            return $force_return;
5686
        }
5687
5688
        return -1;
5689
    }
5690
5691
    /**
5692
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5693
     *
5694
     * @return bool True on success, false on failure
5695
     */
5696
    public function update_display_order()
5697
    {
5698
        $course_id = api_get_course_int_id();
5699
        $table = Database::get_course_table(TABLE_LP_MAIN);
5700
        $sql = "SELECT * FROM $table
5701
                WHERE c_id = $course_id
5702
                ORDER BY display_order";
5703
        $res = Database::query($sql);
5704
        if ($res === false) {
5705
            return false;
5706
        }
5707
5708
        $num = Database::num_rows($res);
5709
        // First check the order is correct, globally (might be wrong because
5710
        // of versions < 1.8.4).
5711
        if ($num > 0) {
5712
            $i = 1;
5713
            while ($row = Database::fetch_array($res)) {
5714
                if ($row['display_order'] != $i) {
5715
                    // If we find a gap in the order, we need to fix it.
5716
                    $sql = "UPDATE $table SET display_order = $i
5717
                            WHERE iid = ".$row['iid'];
5718
                    Database::query($sql);
5719
                }
5720
                $i++;
5721
            }
5722
        }
5723
5724
        return true;
5725
    }
5726
5727
    /**
5728
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5729
     *
5730
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5731
     */
5732
    public function update_reinit()
5733
    {
5734
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5735
        $sql = "SELECT * FROM $lp_table
5736
                WHERE iid = ".$this->get_id();
5737
        $res = Database::query($sql);
5738
        if (Database::num_rows($res) > 0) {
5739
            $row = Database::fetch_array($res);
5740
            $force = $row['prevent_reinit'];
5741
            if ($force == 1) {
5742
                $force = 0;
5743
            } elseif ($force == 0) {
5744
                $force = 1;
5745
            }
5746
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5747
                    WHERE iid = ".$this->get_id();
5748
            Database::query($sql);
5749
            $this->prevent_reinit = $force;
5750
5751
            return $force;
5752
        }
5753
5754
        return -1;
5755
    }
5756
5757
    /**
5758
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5759
     *
5760
     * @return string 'single', 'multi' or 'seriousgame'
5761
     *
5762
     * @author ndiechburg <[email protected]>
5763
     */
5764
    public function get_attempt_mode()
5765
    {
5766
        //Set default value for seriousgame_mode
5767
        if (!isset($this->seriousgame_mode)) {
5768
            $this->seriousgame_mode = 0;
5769
        }
5770
        // Set default value for prevent_reinit
5771
        if (!isset($this->prevent_reinit)) {
5772
            $this->prevent_reinit = 1;
5773
        }
5774
        if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
5775
            return 'seriousgame';
5776
        }
5777
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
5778
            return 'single';
5779
        }
5780
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
5781
            return 'multiple';
5782
        }
5783
5784
        return 'single';
5785
    }
5786
5787
    /**
5788
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5789
     *
5790
     * @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...
5791
     *
5792
     * @return bool
5793
     *
5794
     * @author ndiechburg <[email protected]>
5795
     */
5796
    public function set_attempt_mode($mode)
5797
    {
5798
        switch ($mode) {
5799
            case 'seriousgame':
5800
                $sg_mode = 1;
5801
                $prevent_reinit = 1;
5802
                break;
5803
            case 'single':
5804
                $sg_mode = 0;
5805
                $prevent_reinit = 1;
5806
                break;
5807
            case 'multiple':
5808
                $sg_mode = 0;
5809
                $prevent_reinit = 0;
5810
                break;
5811
            default:
5812
                $sg_mode = 0;
5813
                $prevent_reinit = 0;
5814
                break;
5815
        }
5816
        $this->prevent_reinit = $prevent_reinit;
5817
        $this->seriousgame_mode = $sg_mode;
5818
        $table = Database::get_course_table(TABLE_LP_MAIN);
5819
        $sql = "UPDATE $table SET
5820
                prevent_reinit = $prevent_reinit ,
5821
                seriousgame_mode = $sg_mode
5822
                WHERE iid = ".$this->get_id();
5823
        $res = Database::query($sql);
5824
        if ($res) {
5825
            return true;
5826
        } else {
5827
            return false;
5828
        }
5829
    }
5830
5831
    /**
5832
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5833
     *
5834
     * @author ndiechburg <[email protected]>
5835
     */
5836
    public function switch_attempt_mode()
5837
    {
5838
        $mode = $this->get_attempt_mode();
5839
        switch ($mode) {
5840
            case 'single':
5841
                $next_mode = 'multiple';
5842
                break;
5843
            case 'multiple':
5844
                $next_mode = 'seriousgame';
5845
                break;
5846
            case 'seriousgame':
5847
            default:
5848
                $next_mode = 'single';
5849
                break;
5850
        }
5851
        $this->set_attempt_mode($next_mode);
5852
    }
5853
5854
    /**
5855
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5856
     * but possibility to do again a completed item.
5857
     *
5858
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5859
     *
5860
     * @author ndiechburg <[email protected]>
5861
     */
5862
    public function set_seriousgame_mode()
5863
    {
5864
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5865
        $sql = "SELECT * FROM $lp_table
5866
                WHERE iid = ".$this->get_id();
5867
        $res = Database::query($sql);
5868
        if (Database::num_rows($res) > 0) {
5869
            $row = Database::fetch_array($res);
5870
            $force = $row['seriousgame_mode'];
5871
            if ($force == 1) {
5872
                $force = 0;
5873
            } elseif ($force == 0) {
5874
                $force = 1;
5875
            }
5876
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5877
			        WHERE iid = ".$this->get_id();
5878
            Database::query($sql);
5879
            $this->seriousgame_mode = $force;
5880
5881
            return $force;
5882
        }
5883
5884
        return -1;
5885
    }
5886
5887
    /**
5888
     * Updates the "scorm_debug" value that shows or hide the debug window.
5889
     *
5890
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5891
     */
5892
    public function update_scorm_debug()
5893
    {
5894
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5895
        $sql = "SELECT * FROM $lp_table
5896
                WHERE iid = ".$this->get_id();
5897
        $res = Database::query($sql);
5898
        if (Database::num_rows($res) > 0) {
5899
            $row = Database::fetch_array($res);
5900
            $force = $row['debug'];
5901
            if ($force == 1) {
5902
                $force = 0;
5903
            } elseif ($force == 0) {
5904
                $force = 1;
5905
            }
5906
            $sql = "UPDATE $lp_table SET debug = $force
5907
                    WHERE iid = ".$this->get_id();
5908
            Database::query($sql);
5909
            $this->scorm_debug = $force;
5910
5911
            return $force;
5912
        }
5913
5914
        return -1;
5915
    }
5916
5917
    /**
5918
     * Function that makes a call to the function sort_tree_array and create_tree_array.
5919
     *
5920
     * @author Kevin Van Den Haute
5921
     *
5922
     * @param  array
5923
     */
5924
    public function tree_array($array)
5925
    {
5926
        $array = $this->sort_tree_array($array);
5927
        $this->create_tree_array($array);
5928
    }
5929
5930
    /**
5931
     * Creates an array with the elements of the learning path tree in it.
5932
     *
5933
     * @author Kevin Van Den Haute
5934
     *
5935
     * @param array $array
5936
     * @param int   $parent
5937
     * @param int   $depth
5938
     * @param array $tmp
5939
     */
5940
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
5941
    {
5942
        if (is_array($array)) {
5943
            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...
5944
                if ($array[$i]['parent_item_id'] == $parent) {
5945
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
5946
                        $tmp[] = $array[$i]['parent_item_id'];
5947
                        $depth++;
5948
                    }
5949
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
5950
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
5951
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
5952
5953
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
5954
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
5955
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
5956
                    $this->arrMenu[] = [
5957
                        'id' => $array[$i]['id'],
5958
                        'ref' => $ref,
5959
                        'item_type' => $array[$i]['item_type'],
5960
                        'title' => $array[$i]['title'],
5961
                        'title_raw' => $array[$i]['title_raw'],
5962
                        'path' => $path,
5963
                        'description' => $array[$i]['description'],
5964
                        'parent_item_id' => $array[$i]['parent_item_id'],
5965
                        'previous_item_id' => $array[$i]['previous_item_id'],
5966
                        'next_item_id' => $array[$i]['next_item_id'],
5967
                        'min_score' => $array[$i]['min_score'],
5968
                        'max_score' => $array[$i]['max_score'],
5969
                        'mastery_score' => $array[$i]['mastery_score'],
5970
                        'display_order' => $array[$i]['display_order'],
5971
                        'prerequisite' => $preq,
5972
                        'depth' => $depth,
5973
                        'audio' => $audio,
5974
                        'prerequisite_min_score' => $prerequisiteMinScore,
5975
                        'prerequisite_max_score' => $prerequisiteMaxScore,
5976
                    ];
5977
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
5978
                }
5979
            }
5980
        }
5981
    }
5982
5983
    /**
5984
     * Sorts a multi dimensional array by parent id and display order.
5985
     *
5986
     * @author Kevin Van Den Haute
5987
     *
5988
     * @param array $array (array with al the learning path items in it)
5989
     *
5990
     * @return array
5991
     */
5992
    public function sort_tree_array($array)
5993
    {
5994
        foreach ($array as $key => $row) {
5995
            $parent[$key] = $row['parent_item_id'];
5996
            $position[$key] = $row['display_order'];
5997
        }
5998
5999
        if (count($array) > 0) {
6000
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
6001
        }
6002
6003
        return $array;
6004
    }
6005
6006
    /**
6007
     * Function that creates a html list of learning path items so that we can add audio files to them.
6008
     *
6009
     * @author Kevin Van Den Haute
6010
     *
6011
     * @return string
6012
     */
6013
    public function overview()
6014
    {
6015
        $return = '';
6016
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
6017
6018
        // we need to start a form when we want to update all the mp3 files
6019
        if ($update_audio == 'true') {
6020
            $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">';
6021
        }
6022
        $return .= '<div id="message"></div>';
6023
        if (count($this->items) == 0) {
6024
            $return .= Display::return_message(get_lang('YouShouldAddItemsBeforeAttachAudio'), 'normal');
6025
        } else {
6026
            $return_audio = '<table class="table table-hover table-striped data_table">';
6027
            $return_audio .= '<tr>';
6028
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
6029
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
6030
            $return_audio .= '</tr>';
6031
6032
            if ($update_audio != 'true') {
6033
                $return .= '<div class="col-md-12">';
6034
                $return .= self::return_new_tree($update_audio);
6035
                $return .= '</div>';
6036
                $return .= Display::div(
6037
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
6038
                    ['style' => 'float:left; margin-top:15px;width:100%']
6039
                );
6040
            } else {
6041
                $return_audio .= self::return_new_tree($update_audio);
6042
                $return .= $return_audio.'</table>';
6043
            }
6044
6045
            // We need to close the form when we are updating the mp3 files.
6046
            if ($update_audio == 'true') {
6047
                $return .= '<div class="footer-audio">';
6048
                $return .= Display::button(
6049
                    'save_audio',
6050
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('SaveAudioAndOrganization'),
6051
                    ['class' => 'btn btn-primary', 'type' => 'submit']
6052
                );
6053
                $return .= '</div>';
6054
            }
6055
        }
6056
6057
        // We need to close the form when we are updating the mp3 files.
6058
        if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
6059
            $return .= '</form>';
6060
        }
6061
6062
        return $return;
6063
    }
6064
6065
    /**
6066
     * @param string $update_audio
6067
     *
6068
     * @return array
6069
     */
6070
    public function processBuildMenuElements($update_audio = 'false')
6071
    {
6072
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
6073
        $arrLP = $this->getItemsForForm();
6074
6075
        $this->tree_array($arrLP);
6076
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
6077
        unset($this->arrMenu);
6078
        $default_data = null;
6079
        $default_content = null;
6080
        $elements = [];
6081
        $return_audio = null;
6082
        $iconPath = api_get_path(SYS_CODE_PATH).'img/';
6083
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
6084
        $countItems = count($arrLP);
6085
6086
        $upIcon = Display::return_icon(
6087
            'up.png',
6088
            get_lang('Up'),
6089
            [],
6090
            ICON_SIZE_TINY
6091
        );
6092
6093
        $disableUpIcon = Display::return_icon(
6094
            'up_na.png',
6095
            get_lang('Up'),
6096
            [],
6097
            ICON_SIZE_TINY
6098
        );
6099
6100
        $downIcon = Display::return_icon(
6101
            'down.png',
6102
            get_lang('Down'),
6103
            [],
6104
            ICON_SIZE_TINY
6105
        );
6106
6107
        $disableDownIcon = Display::return_icon(
6108
            'down_na.png',
6109
            get_lang('Down'),
6110
            [],
6111
            ICON_SIZE_TINY
6112
        );
6113
6114
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
6115
6116
        $pluginCalendar = api_get_plugin_setting('learning_calendar', 'enabled') === 'true';
6117
        $plugin = null;
6118
        if ($pluginCalendar) {
6119
            $plugin = LearningCalendarPlugin::create();
6120
        }
6121
6122
        for ($i = 0; $i < $countItems; $i++) {
6123
            $parent_id = $arrLP[$i]['parent_item_id'];
6124
            $title = $arrLP[$i]['title'];
6125
            $title_cut = $arrLP[$i]['title_raw'];
6126
            if ($show === false) {
6127
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
6128
            }
6129
            // Link for the documents
6130
            if ($arrLP[$i]['item_type'] === 'document' || $arrLP[$i]['item_type'] == TOOL_READOUT_TEXT) {
6131
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6132
                $title_cut = Display::url(
6133
                    $title_cut,
6134
                    $url,
6135
                    [
6136
                        'class' => 'ajax moved',
6137
                        'data-title' => $title,
6138
                        'title' => $title,
6139
                    ]
6140
                );
6141
            }
6142
6143
            // Detect if type is FINAL_ITEM to set path_id to SESSION
6144
            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6145
                Session::write('pathItem', $arrLP[$i]['path']);
6146
            }
6147
6148
            $oddClass = 'row_even';
6149
            if (($i % 2) == 0) {
6150
                $oddClass = 'row_odd';
6151
            }
6152
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
6153
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
6154
6155
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
6156
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
6157
            } else {
6158
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
6159
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
6160
                } else {
6161
                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
6162
                        $icon = Display::return_icon('certificate.png');
6163
                    } else {
6164
                        $icon = Display::return_icon('folder_document.gif');
6165
                    }
6166
                }
6167
            }
6168
6169
            // The audio column.
6170
            $return_audio .= '<td align="left" style="padding-left:10px;">';
6171
            $audio = '';
6172
            if (!$update_audio || $update_audio != 'true') {
6173
                if (empty($arrLP[$i]['audio'])) {
6174
                    $audio .= '';
6175
                }
6176
            } else {
6177
                $types = self::getChapterTypes();
6178
                if (!in_array($arrLP[$i]['item_type'], $types)) {
6179
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
6180
                    if (!empty($arrLP[$i]['audio'])) {
6181
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
6182
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('RemoveAudio');
6183
                    }
6184
                }
6185
            }
6186
6187
            $return_audio .= Display::span($icon.' '.$title).
6188
                Display::tag(
6189
                    'td',
6190
                    $audio,
6191
                    ['style' => '']
6192
                );
6193
            $return_audio .= '</td>';
6194
            $move_icon = '';
6195
            $move_item_icon = '';
6196
            $edit_icon = '';
6197
            $delete_icon = '';
6198
            $audio_icon = '';
6199
            $prerequisities_icon = '';
6200
            $forumIcon = '';
6201
            $previewIcon = '';
6202
            $pluginCalendarIcon = '';
6203
            $orderIcons = '';
6204
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
6205
6206
            if ($is_allowed_to_edit) {
6207
                if (!$update_audio || $update_audio != 'true') {
6208
                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
6209
                        $move_icon .= '<a class="moved" href="#">';
6210
                        $move_icon .= Display::return_icon(
6211
                            'move_everywhere.png',
6212
                            get_lang('Move'),
6213
                            [],
6214
                            ICON_SIZE_TINY
6215
                        );
6216
                        $move_icon .= '</a>';
6217
                    }
6218
                }
6219
6220
                // No edit for this item types
6221
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6222
                    if ($arrLP[$i]['item_type'] != 'dir') {
6223
                        $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">';
6224
                        $edit_icon .= Display::return_icon(
6225
                            'edit.png',
6226
                            get_lang('LearnpathEditModule'),
6227
                            [],
6228
                            ICON_SIZE_TINY
6229
                        );
6230
                        $edit_icon .= '</a>';
6231
6232
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6233
                            $forumThread = null;
6234
                            if (isset($this->items[$arrLP[$i]['id']])) {
6235
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6236
                                    $this->course_int_id,
6237
                                    $this->lp_session_id
6238
                                );
6239
                            }
6240
                            if ($forumThread) {
6241
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6242
                                        'action' => 'dissociate_forum',
6243
                                        'id' => $arrLP[$i]['id'],
6244
                                        'lp_id' => $this->lp_id,
6245
                                    ]);
6246
                                $forumIcon = Display::url(
6247
                                    Display::return_icon(
6248
                                        'forum.png',
6249
                                        get_lang('DissociateForumToLPItem'),
6250
                                        [],
6251
                                        ICON_SIZE_TINY
6252
                                    ),
6253
                                    $forumIconUrl,
6254
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6255
                                );
6256
                            } else {
6257
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6258
                                        'action' => 'create_forum',
6259
                                        'id' => $arrLP[$i]['id'],
6260
                                        'lp_id' => $this->lp_id,
6261
                                    ]);
6262
                                $forumIcon = Display::url(
6263
                                    Display::return_icon(
6264
                                        'forum.png',
6265
                                        get_lang('AssociateForumToLPItem'),
6266
                                        [],
6267
                                        ICON_SIZE_TINY
6268
                                    ),
6269
                                    $forumIconUrl,
6270
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6271
                                );
6272
                            }
6273
                        }
6274
                    } else {
6275
                        $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">';
6276
                        $edit_icon .= Display::return_icon(
6277
                            'edit.png',
6278
                            get_lang('LearnpathEditModule'),
6279
                            [],
6280
                            ICON_SIZE_TINY
6281
                        );
6282
                        $edit_icon .= '</a>';
6283
                    }
6284
                } else {
6285
                    if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6286
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6287
                        $edit_icon .= Display::return_icon(
6288
                            'edit.png',
6289
                            get_lang('Edit'),
6290
                            [],
6291
                            ICON_SIZE_TINY
6292
                        );
6293
                        $edit_icon .= '</a>';
6294
                    }
6295
                }
6296
6297
                if ($pluginCalendar) {
6298
                    $pluginLink = $pluginUrl.
6299
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6300
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6301
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6302
                    if ($itemInfo && $itemInfo['value'] == 1) {
6303
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6304
                    }
6305
                    $pluginCalendarIcon = Display::url(
6306
                        $iconCalendar,
6307
                        $pluginLink,
6308
                        ['class' => 'btn btn-default']
6309
                    );
6310
                }
6311
6312
                if ($arrLP[$i]['item_type'] != 'final_item') {
6313
                    $orderIcons = Display::url(
6314
                        $upIcon,
6315
                        'javascript:void(0)',
6316
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
6317
                    );
6318
                    $orderIcons .= Display::url(
6319
                        $downIcon,
6320
                        'javascript:void(0)',
6321
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
6322
                    );
6323
                }
6324
6325
                $delete_icon .= ' <a
6326
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
6327
                    onclick="return confirmation(\''.addslashes($title).'\');"
6328
                    class="btn btn-default">';
6329
                $delete_icon .= Display::return_icon(
6330
                    'delete.png',
6331
                    get_lang('LearnpathDeleteModule'),
6332
                    [],
6333
                    ICON_SIZE_TINY
6334
                );
6335
                $delete_icon .= '</a>';
6336
6337
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6338
                $previewImage = Display::return_icon(
6339
                    'preview_view.png',
6340
                    get_lang('Preview'),
6341
                    [],
6342
                    ICON_SIZE_TINY
6343
                );
6344
6345
                switch ($arrLP[$i]['item_type']) {
6346
                    case TOOL_DOCUMENT:
6347
                    case TOOL_LP_FINAL_ITEM:
6348
                    case TOOL_READOUT_TEXT:
6349
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6350
                        $previewIcon = Display::url(
6351
                            $previewImage,
6352
                            $urlPreviewLink,
6353
                            [
6354
                                'target' => '_blank',
6355
                                'class' => 'btn btn-default',
6356
                                'data-title' => $arrLP[$i]['title'],
6357
                                'title' => $arrLP[$i]['title'],
6358
                            ]
6359
                        );
6360
                        break;
6361
                    case TOOL_THREAD:
6362
                    case TOOL_FORUM:
6363
                    case TOOL_QUIZ:
6364
                    case TOOL_STUDENTPUBLICATION:
6365
                    case TOOL_LP_FINAL_ITEM:
6366
                    case TOOL_LINK:
6367
                        $class = 'btn btn-default';
6368
                        $target = '_blank';
6369
                        $link = self::rl_get_resource_link_for_learnpath(
6370
                            $this->course_int_id,
6371
                            $this->lp_id,
6372
                            $arrLP[$i]['id'],
6373
                            0
6374
                        );
6375
                        $previewIcon = Display::url(
6376
                            $previewImage,
6377
                            $link,
6378
                            [
6379
                                'class' => $class,
6380
                                'data-title' => $arrLP[$i]['title'],
6381
                                'title' => $arrLP[$i]['title'],
6382
                                'target' => $target,
6383
                            ]
6384
                        );
6385
                        break;
6386
                    default:
6387
                        $previewIcon = Display::url(
6388
                            $previewImage,
6389
                            $url.'&action=view_item',
6390
                            ['class' => 'btn btn-default', 'target' => '_blank']
6391
                        );
6392
                        break;
6393
                }
6394
6395
                if ($arrLP[$i]['item_type'] != 'dir') {
6396
                    $prerequisities_icon = Display::url(
6397
                        Display::return_icon(
6398
                            'accept.png',
6399
                            get_lang('LearnpathPrerequisites'),
6400
                            [],
6401
                            ICON_SIZE_TINY
6402
                        ),
6403
                        $url.'&action=edit_item_prereq',
6404
                        ['class' => 'btn btn-default']
6405
                    );
6406
                    if ($arrLP[$i]['item_type'] != 'final_item') {
6407
                        $move_item_icon = Display::url(
6408
                            Display::return_icon(
6409
                                'move.png',
6410
                                get_lang('Move'),
6411
                                [],
6412
                                ICON_SIZE_TINY
6413
                            ),
6414
                            $url.'&action=move_item',
6415
                            ['class' => 'btn btn-default']
6416
                        );
6417
                    }
6418
                    $audio_icon = Display::url(
6419
                        Display::return_icon(
6420
                            'audio.png',
6421
                            get_lang('UplUpload'),
6422
                            [],
6423
                            ICON_SIZE_TINY
6424
                        ),
6425
                        $url.'&action=add_audio',
6426
                        ['class' => 'btn btn-default']
6427
                    );
6428
                }
6429
            }
6430
            if ($update_audio != 'true') {
6431
                $row = $move_icon.' '.$icon.
6432
                    Display::span($title_cut).
6433
                    Display::tag(
6434
                        'div',
6435
                        "<div class=\"btn-group btn-group-xs\">
6436
                                    $previewIcon
6437
                                    $audio
6438
                                    $edit_icon
6439
                                    $pluginCalendarIcon
6440
                                    $forumIcon
6441
                                    $prerequisities_icon
6442
                                    $move_item_icon
6443
                                    $audio_icon
6444
                                    $orderIcons
6445
                                    $delete_icon
6446
                                </div>",
6447
                        ['class' => 'btn-toolbar button_actions']
6448
                    );
6449
            } else {
6450
                $row =
6451
                    Display::span($title.$icon).
6452
                    Display::span($audio, ['class' => 'button_actions']);
6453
            }
6454
6455
            $default_data[$arrLP[$i]['id']] = $row;
6456
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6457
6458
            if (empty($parent_id)) {
6459
                $elements[$arrLP[$i]['id']]['data'] = $row;
6460
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6461
            } else {
6462
                $parent_arrays = [];
6463
                if ($arrLP[$i]['depth'] > 1) {
6464
                    // Getting list of parents
6465
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6466
                        foreach ($arrLP as $item) {
6467
                            if ($item['id'] == $parent_id) {
6468
                                if ($item['parent_item_id'] == 0) {
6469
                                    $parent_id = $item['id'];
6470
                                    break;
6471
                                } else {
6472
                                    $parent_id = $item['parent_item_id'];
6473
                                    if (empty($parent_arrays)) {
6474
                                        $parent_arrays[] = intval($item['id']);
6475
                                    }
6476
                                    $parent_arrays[] = $parent_id;
6477
                                    break;
6478
                                }
6479
                            }
6480
                        }
6481
                    }
6482
                }
6483
6484
                if (!empty($parent_arrays)) {
6485
                    $parent_arrays = array_reverse($parent_arrays);
6486
                    $val = '$elements';
6487
                    $x = 0;
6488
                    foreach ($parent_arrays as $item) {
6489
                        if ($x != count($parent_arrays) - 1) {
6490
                            $val .= '["'.$item.'"]["children"]';
6491
                        } else {
6492
                            $val .= '["'.$item.'"]["children"]';
6493
                        }
6494
                        $x++;
6495
                    }
6496
                    $val .= "";
6497
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6498
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6499
                } else {
6500
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6501
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6502
                }
6503
            }
6504
        }
6505
6506
        return [
6507
            'elements' => $elements,
6508
            'default_data' => $default_data,
6509
            'default_content' => $default_content,
6510
            'return_audio' => $return_audio,
6511
        ];
6512
    }
6513
6514
    /**
6515
     * @param string $updateAudio true/false strings
6516
     *
6517
     * @return string
6518
     */
6519
    public function returnLpItemList($updateAudio)
6520
    {
6521
        $result = $this->processBuildMenuElements($updateAudio);
6522
6523
        $html = self::print_recursive(
6524
            $result['elements'],
6525
            $result['default_data'],
6526
            $result['default_content']
6527
        );
6528
6529
        if (!empty($html)) {
6530
            $html .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6531
        }
6532
6533
        return $html;
6534
    }
6535
6536
    /**
6537
     * @param string $update_audio
6538
     * @param bool   $drop_element_here
6539
     *
6540
     * @return string
6541
     */
6542
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6543
    {
6544
        $result = $this->processBuildMenuElements($update_audio);
6545
6546
        $list = '<ul id="lp_item_list">';
6547
        $tree = $this->print_recursive(
6548
            $result['elements'],
6549
            $result['default_data'],
6550
            $result['default_content']
6551
        );
6552
6553
        if (!empty($tree)) {
6554
            $list .= $tree;
6555
        } else {
6556
            if ($drop_element_here) {
6557
                $list .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6558
            }
6559
        }
6560
        $list .= '</ul>';
6561
6562
        $return = Display::panelCollapse(
6563
            $this->name,
6564
            $list,
6565
            'scorm-list',
6566
            null,
6567
            'scorm-list-accordion',
6568
            'scorm-list-collapse'
6569
        );
6570
6571
        if ($update_audio === 'true') {
6572
            $return = $result['return_audio'];
6573
        }
6574
6575
        return $return;
6576
    }
6577
6578
    /**
6579
     * @param array $elements
6580
     * @param array $default_data
6581
     * @param array $default_content
6582
     *
6583
     * @return string
6584
     */
6585
    public function print_recursive($elements, $default_data, $default_content)
6586
    {
6587
        $return = '';
6588
        foreach ($elements as $key => $item) {
6589
            if (isset($item['load_data']) || empty($item['data'])) {
6590
                $item['data'] = $default_data[$item['load_data']];
6591
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6592
            }
6593
            $sub_list = '';
6594
            if (isset($item['type']) && $item['type'] === 'dir') {
6595
                // empty value
6596
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6597
            }
6598
            if (empty($item['children'])) {
6599
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6600
                $active = null;
6601
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6602
                    $active = 'active';
6603
                }
6604
                $return .= Display::tag(
6605
                    'li',
6606
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6607
                    ['id' => $key, 'class' => 'record li_container']
6608
                );
6609
            } else {
6610
                // Sections
6611
                $data = '';
6612
                if (isset($item['children'])) {
6613
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6614
                }
6615
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6616
                $return .= Display::tag(
6617
                    'li',
6618
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6619
                    ['id' => $key, 'class' => 'record li_container']
6620
                );
6621
            }
6622
        }
6623
6624
        return $return;
6625
    }
6626
6627
    /**
6628
     * This function builds the action menu.
6629
     *
6630
     * @param bool   $returnString           Optional
6631
     * @param bool   $showRequirementButtons Optional. Allow show the requirements button
6632
     * @param bool   $isConfigPage           Optional. If is the config page, show the edit button
6633
     * @param bool   $allowExpand            Optional. Allow show the expand/contract button
6634
     * @param string $action
6635
     * @param array  $extraField
6636
     *
6637
     * @return string
6638
     */
6639
    public function build_action_menu(
6640
        $returnString = false,
6641
        $showRequirementButtons = true,
6642
        $isConfigPage = false,
6643
        $allowExpand = true,
6644
        $action = '',
6645
        $extraField = []
6646
    ) {
6647
        $actionsRight = '';
6648
        $lpId = $this->lp_id;
6649
        if (!isset($extraField['backTo']) && empty($extraField['backTo'])) {
6650
            $back = Display::url(
6651
                Display::return_icon(
6652
                    'back.png',
6653
                    get_lang('ReturnToLearningPaths'),
6654
                    '',
6655
                    ICON_SIZE_MEDIUM
6656
                ),
6657
                'lp_controller.php?'.api_get_cidreq()
6658
            );
6659
        } else {
6660
            $back = Display::url(
6661
                Display::return_icon(
6662
                    'back.png',
6663
                    get_lang('Back'),
6664
                    '',
6665
                    ICON_SIZE_MEDIUM
6666
                ),
6667
                $extraField['backTo']
6668
            );
6669
        }
6670
6671
        /*if ($backToBuild) {
6672
            $back = Display::url(
6673
                Display::return_icon(
6674
                    'back.png',
6675
                    get_lang('GoBack'),
6676
                    '',
6677
                    ICON_SIZE_MEDIUM
6678
                ),
6679
                "lp_controller.php?action=add_item&type=step&lp_id=$lpId&".api_get_cidreq()
6680
            );
6681
        }*/
6682
6683
        $actionsLeft = $back;
6684
6685
        $actionsLeft .= Display::url(
6686
            Display::return_icon(
6687
                'preview_view.png',
6688
                get_lang('Preview'),
6689
                '',
6690
                ICON_SIZE_MEDIUM
6691
            ),
6692
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6693
                'action' => 'view',
6694
                'lp_id' => $lpId,
6695
                'isStudentView' => 'true',
6696
            ])
6697
        );
6698
6699
        $actionsLeft .= Display::url(
6700
            Display::return_icon(
6701
                'upload_audio.png',
6702
                get_lang('UpdateAllAudioFragments'),
6703
                '',
6704
                ICON_SIZE_MEDIUM
6705
            ),
6706
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6707
                'action' => 'admin_view',
6708
                'lp_id' => $lpId,
6709
                'updateaudio' => 'true',
6710
            ])
6711
        );
6712
6713
        $subscriptionSettings = self::getSubscriptionSettings();
6714
6715
        $request = api_request_uri();
6716
        if (strpos($request, 'edit') === false) {
6717
            $actionsLeft .= Display::url(
6718
                Display::return_icon(
6719
                    'settings.png',
6720
                    get_lang('CourseSettings'),
6721
                    '',
6722
                    ICON_SIZE_MEDIUM
6723
                ),
6724
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6725
                    'action' => 'edit',
6726
                    'lp_id' => $lpId,
6727
                ])
6728
            );
6729
        }
6730
6731
        if ((strpos($request, 'build') === false &&
6732
            strpos($request, 'add_item') === false) ||
6733
            in_array($action, ['add_audio'])
6734
        ) {
6735
            $actionsLeft .= Display::url(
6736
                Display::return_icon(
6737
                    'edit.png',
6738
                    get_lang('Edit'),
6739
                    '',
6740
                    ICON_SIZE_MEDIUM
6741
                ),
6742
                'lp_controller.php?'.http_build_query([
6743
                    'action' => 'build',
6744
                    'lp_id' => $lpId,
6745
                ]).'&'.api_get_cidreq()
6746
            );
6747
        }
6748
6749
        if (strpos(api_get_self(), 'lp_subscribe_users.php') === false) {
6750
            if ($this->subscribeUsers == 1 &&
6751
                $subscriptionSettings['allow_add_users_to_lp']) {
6752
                $actionsLeft .= Display::url(
6753
                    Display::return_icon(
6754
                        'user.png',
6755
                        get_lang('SubscribeUsersToLp'),
6756
                        '',
6757
                        ICON_SIZE_MEDIUM
6758
                    ),
6759
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=".$lpId."&".api_get_cidreq()
6760
                );
6761
            }
6762
        }
6763
6764
        if ($allowExpand) {
6765
            $actionsLeft .= Display::url(
6766
                Display::return_icon(
6767
                    'expand.png',
6768
                    get_lang('Expand'),
6769
                    ['id' => 'expand'],
6770
                    ICON_SIZE_MEDIUM
6771
                ).
6772
                Display::return_icon(
6773
                    'contract.png',
6774
                    get_lang('Collapse'),
6775
                    ['id' => 'contract', 'class' => 'hide'],
6776
                    ICON_SIZE_MEDIUM
6777
                ),
6778
                '#',
6779
                ['role' => 'button', 'id' => 'hide_bar_template']
6780
            );
6781
        }
6782
6783
        if ($showRequirementButtons) {
6784
            $buttons = [
6785
                [
6786
                    'title' => get_lang('SetPrerequisiteForEachItem'),
6787
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6788
                        'action' => 'set_previous_step_as_prerequisite',
6789
                        'lp_id' => $lpId,
6790
                    ]),
6791
                ],
6792
                [
6793
                    'title' => get_lang('ClearAllPrerequisites'),
6794
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6795
                        'action' => 'clear_prerequisites',
6796
                        'lp_id' => $lpId,
6797
                    ]),
6798
                ],
6799
            ];
6800
            $actionsRight = Display::groupButtonWithDropDown(
6801
                get_lang('PrerequisitesOptions'),
6802
                $buttons,
6803
                true
6804
            );
6805
        }
6806
6807
        // see  BT#17943
6808
        if (api_is_platform_admin()) {
6809
            if (isset($extraField['authorlp'])) {
6810
                $actionsLeft .= Display::url(
6811
                    Display::return_icon(
6812
                        'add-groups.png',
6813
                        get_lang('Author'),
6814
                        '',
6815
                        ICON_SIZE_MEDIUM
6816
                    ),
6817
                    'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6818
                        'action' => 'author_view',
6819
                        'lp_id' => $lpId,
6820
                    ])
6821
                );
6822
            }
6823
        }
6824
6825
        $toolbar = Display::toolbarAction(
6826
            'actions-lp-controller',
6827
            [$actionsLeft, $actionsRight]
6828
        );
6829
6830
        if ($returnString) {
6831
            return $toolbar;
6832
        }
6833
6834
        echo $toolbar;
6835
    }
6836
6837
    /**
6838
     * Creates the default learning path folder.
6839
     *
6840
     * @param array $course
6841
     * @param int   $creatorId
6842
     *
6843
     * @return bool
6844
     */
6845
    public static function generate_learning_path_folder($course, $creatorId = 0)
6846
    {
6847
        // Creating learning_path folder
6848
        $dir = '/learning_path';
6849
        $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6850
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6851
6852
        $folder = false;
6853
        if (!is_dir($filepath.'/'.$dir)) {
6854
            $folderData = create_unexisting_directory(
6855
                $course,
6856
                $creatorId,
6857
                0,
6858
                null,
6859
                0,
6860
                $filepath,
6861
                $dir,
6862
                get_lang('LearningPaths'),
6863
                0
6864
            );
6865
            if (!empty($folderData)) {
6866
                $folder = true;
6867
            }
6868
        } else {
6869
            $folder = true;
6870
        }
6871
6872
        return $folder;
6873
    }
6874
6875
    /**
6876
     * @param array  $course
6877
     * @param string $lp_name
6878
     * @param int    $creatorId
6879
     *
6880
     * @return array
6881
     */
6882
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6883
    {
6884
        $filepath = '';
6885
        $dir = '/learning_path/';
6886
6887
        if (empty($lp_name)) {
6888
            $lp_name = $this->name;
6889
        }
6890
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6891
        $folder = self::generate_learning_path_folder($course, $creatorId);
6892
6893
        // Limits title size
6894
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6895
        $dir = $dir.$title;
6896
6897
        // Creating LP folder
6898
        $documentId = null;
6899
        if ($folder) {
6900
            $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6901
            if (!is_dir($filepath.'/'.$dir)) {
6902
                $folderData = create_unexisting_directory(
6903
                    $course,
6904
                    $creatorId,
6905
                    0,
6906
                    0,
6907
                    0,
6908
                    $filepath,
6909
                    $dir,
6910
                    $lp_name
6911
                );
6912
                if (!empty($folderData)) {
6913
                    $folder = true;
6914
                }
6915
6916
                $documentId = $folderData['id'];
6917
            } else {
6918
                $folder = true;
6919
            }
6920
            $dir = $dir.'/';
6921
            if ($folder) {
6922
                $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6923
            }
6924
        }
6925
6926
        if (empty($documentId)) {
6927
            $dir = api_remove_trailing_slash($dir);
6928
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6929
        }
6930
6931
        $array = [
6932
            'dir' => $dir,
6933
            'filepath' => $filepath,
6934
            'folder' => $folder,
6935
            'id' => $documentId,
6936
        ];
6937
6938
        return $array;
6939
    }
6940
6941
    /**
6942
     * Create a new document //still needs some finetuning.
6943
     *
6944
     * @param array  $courseInfo
6945
     * @param string $content
6946
     * @param string $title
6947
     * @param string $extension
6948
     * @param int    $parentId
6949
     * @param int    $creatorId  creator id
6950
     *
6951
     * @return int
6952
     */
6953
    public function create_document(
6954
        $courseInfo,
6955
        $content = '',
6956
        $title = '',
6957
        $extension = 'html',
6958
        $parentId = 0,
6959
        $creatorId = 0
6960
    ) {
6961
        if (!empty($courseInfo)) {
6962
            $course_id = $courseInfo['real_id'];
6963
        } else {
6964
            $course_id = api_get_course_int_id();
6965
        }
6966
6967
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6968
        $sessionId = api_get_session_id();
6969
6970
        // Generates folder
6971
        $result = $this->generate_lp_folder($courseInfo);
6972
        $dir = $result['dir'];
6973
6974
        if (empty($parentId) || $parentId == '/') {
6975
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6976
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6977
6978
            if ($parentId === '/') {
6979
                $dir = '/';
6980
            }
6981
6982
            // Please, do not modify this dirname formatting.
6983
            if (strstr($dir, '..')) {
6984
                $dir = '/';
6985
            }
6986
6987
            if (!empty($dir[0]) && $dir[0] == '.') {
6988
                $dir = substr($dir, 1);
6989
            }
6990
            if (!empty($dir[0]) && $dir[0] != '/') {
6991
                $dir = '/'.$dir;
6992
            }
6993
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
6994
                $dir .= '/';
6995
            }
6996
        } else {
6997
            $parentInfo = DocumentManager::get_document_data_by_id(
6998
                $parentId,
6999
                $courseInfo['code']
7000
            );
7001
            if (!empty($parentInfo)) {
7002
                $dir = $parentInfo['path'].'/';
7003
            }
7004
        }
7005
7006
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7007
        if (!is_dir($filepath)) {
7008
            $dir = '/';
7009
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7010
        }
7011
7012
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
7013
        // is already escaped twice when it gets here.
7014
        $originalTitle = !empty($title) ? $title : $_POST['title'];
7015
        if (!empty($title)) {
7016
            $title = api_replace_dangerous_char(stripslashes($title));
7017
        } else {
7018
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
7019
        }
7020
7021
        $title = disable_dangerous_file($title);
7022
        $filename = $title;
7023
        $content = !empty($content) ? $content : $_POST['content_lp'];
7024
        $tmp_filename = $filename;
7025
7026
        $i = 0;
7027
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
7028
            $tmp_filename = $filename.'_'.++$i;
7029
        }
7030
7031
        $filename = $tmp_filename.'.'.$extension;
7032
        if ($extension == 'html') {
7033
            $content = stripslashes($content);
7034
            $content = str_replace(
7035
                api_get_path(WEB_COURSE_PATH),
7036
                api_get_path(REL_PATH).'courses/',
7037
                $content
7038
            );
7039
7040
            // Change the path of mp3 to absolute.
7041
            // The first regexp deals with :// urls.
7042
            $content = preg_replace(
7043
                "|(flashvars=\"file=)([^:/]+)/|",
7044
                "$1".api_get_path(
7045
                    REL_COURSE_PATH
7046
                ).$courseInfo['path'].'/document/',
7047
                $content
7048
            );
7049
            // The second regexp deals with audio/ urls.
7050
            $content = preg_replace(
7051
                "|(flashvars=\"file=)([^/]+)/|",
7052
                "$1".api_get_path(
7053
                    REL_COURSE_PATH
7054
                ).$courseInfo['path'].'/document/$2/',
7055
                $content
7056
            );
7057
            // For flv player: To prevent edition problem with firefox,
7058
            // we have to use a strange tip (don't blame me please).
7059
            $content = str_replace(
7060
                '</body>',
7061
                '<style type="text/css">body{}</style></body>',
7062
                $content
7063
            );
7064
        }
7065
7066
        if (!file_exists($filepath.$filename)) {
7067
            if ($fp = @fopen($filepath.$filename, 'w')) {
7068
                fputs($fp, $content);
7069
                fclose($fp);
7070
7071
                $file_size = filesize($filepath.$filename);
7072
                $save_file_path = $dir.$filename;
7073
7074
                $document_id = add_document(
7075
                    $courseInfo,
7076
                    $save_file_path,
7077
                    'file',
7078
                    $file_size,
7079
                    $tmp_filename,
7080
                    '',
7081
                    0, //readonly
7082
                    true,
7083
                    null,
7084
                    $sessionId,
7085
                    $creatorId
7086
                );
7087
7088
                if ($document_id) {
7089
                    api_item_property_update(
7090
                        $courseInfo,
7091
                        TOOL_DOCUMENT,
7092
                        $document_id,
7093
                        'DocumentAdded',
7094
                        $creatorId,
7095
                        null,
7096
                        null,
7097
                        null,
7098
                        null,
7099
                        $sessionId
7100
                    );
7101
7102
                    $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7103
                    $new_title = $originalTitle;
7104
7105
                    if ($new_comment || $new_title) {
7106
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7107
                        $ct = '';
7108
                        if ($new_comment) {
7109
                            $ct .= ", comment='".Database::escape_string($new_comment)."'";
7110
                        }
7111
                        if ($new_title) {
7112
                            $ct .= ", title='".Database::escape_string($new_title)."' ";
7113
                        }
7114
7115
                        $sql = "UPDATE ".$tbl_doc." SET ".substr($ct, 1)."
7116
                               WHERE c_id = ".$course_id." AND id = ".$document_id;
7117
                        Database::query($sql);
7118
                    }
7119
                }
7120
7121
                return $document_id;
7122
            }
7123
        }
7124
    }
7125
7126
    /**
7127
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
7128
     *
7129
     * @param array $_course array
7130
     */
7131
    public function edit_document($_course)
7132
    {
7133
        $course_id = api_get_course_int_id();
7134
        $urlAppend = api_get_configuration_value('url_append');
7135
        // Please, do not modify this dirname formatting.
7136
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
7137
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
7138
7139
        if (strstr($dir, '..')) {
7140
            $dir = '/';
7141
        }
7142
7143
        if (isset($dir[0]) && $dir[0] == '.') {
7144
            $dir = substr($dir, 1);
7145
        }
7146
7147
        if (isset($dir[0]) && $dir[0] != '/') {
7148
            $dir = '/'.$dir;
7149
        }
7150
7151
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7152
            $dir .= '/';
7153
        }
7154
7155
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
7156
        if (!is_dir($filepath)) {
7157
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
7158
        }
7159
7160
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
7161
7162
        if (isset($_POST['path']) && !empty($_POST['path'])) {
7163
            $document_id = (int) $_POST['path'];
7164
            $documentInfo = DocumentManager::get_document_data_by_id($document_id, api_get_course_id(), false, null, true);
7165
            if (empty($documentInfo)) {
7166
                // Try with iid
7167
                $table = Database::get_course_table(TABLE_DOCUMENT);
7168
                $sql = "SELECT id, path FROM $table
7169
                        WHERE c_id = $course_id AND iid = $document_id AND path NOT LIKE '%_DELETED_%' ";
7170
                $res_doc = Database::query($sql);
7171
                $row = Database::fetch_array($res_doc);
7172
                if ($row) {
7173
                    $document_id = $row['id'];
7174
                    $documentPath = $row['path'];
7175
                }
7176
            } else {
7177
                $documentPath = $documentInfo['path'];
7178
            }
7179
7180
            $content = stripslashes($_POST['content_lp']);
7181
            $file = $filepath.$documentPath;
7182
7183
            if (!file_exists($file)) {
7184
                return false;
7185
            }
7186
7187
            if ($fp = @fopen($file, 'w')) {
7188
                $content = str_replace(
7189
                    api_get_path(WEB_COURSE_PATH),
7190
                    $urlAppend.api_get_path(REL_COURSE_PATH),
7191
                    $content
7192
                );
7193
                // Change the path of mp3 to absolute.
7194
                // The first regexp deals with :// urls.
7195
                $content = preg_replace(
7196
                    "|(flashvars=\"file=)([^:/]+)/|",
7197
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
7198
                    $content
7199
                );
7200
                // The second regexp deals with audio/ urls.
7201
                $content = preg_replace(
7202
                    "|(flashvars=\"file=)([^:/]+)/|",
7203
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
7204
                    $content
7205
                );
7206
                fputs($fp, $content);
7207
                fclose($fp);
7208
7209
                $sql = "UPDATE $table_doc SET
7210
                            title='".Database::escape_string($_POST['title'])."'
7211
                        WHERE c_id = $course_id AND id = ".$document_id;
7212
                Database::query($sql);
7213
            }
7214
        }
7215
    }
7216
7217
    /**
7218
     * Displays the selected item, with a panel for manipulating the item.
7219
     *
7220
     * @param int    $item_id
7221
     * @param string $msg
7222
     * @param bool   $show_actions
7223
     *
7224
     * @return string
7225
     */
7226
    public function display_item($item_id, $msg = null, $show_actions = true)
7227
    {
7228
        $course_id = api_get_course_int_id();
7229
        $return = '';
7230
        if (is_numeric($item_id)) {
7231
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7232
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
7233
                    WHERE lp.iid = ".intval($item_id);
7234
            $result = Database::query($sql);
7235
            while ($row = Database::fetch_array($result, 'ASSOC')) {
7236
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
7237
7238
                // Prevents wrong parent selection for document, see Bug#1251.
7239
                if ($row['item_type'] != 'dir') {
7240
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
7241
                }
7242
7243
                if ($show_actions) {
7244
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7245
                }
7246
                $return .= '<div style="padding:10px;">';
7247
7248
                if ($msg != '') {
7249
                    $return .= $msg;
7250
                }
7251
7252
                $return .= '<h3>'.$row['title'].'</h3>';
7253
7254
                switch ($row['item_type']) {
7255
                    case TOOL_THREAD:
7256
                        $link = $this->rl_get_resource_link_for_learnpath(
7257
                            $course_id,
7258
                            $row['lp_id'],
7259
                            $item_id,
7260
                            0
7261
                        );
7262
                        $return .= Display::url(
7263
                            get_lang('GoToThread'),
7264
                            $link,
7265
                            ['class' => 'btn btn-primary']
7266
                        );
7267
                        break;
7268
                    case TOOL_FORUM:
7269
                        $return .= Display::url(
7270
                            get_lang('GoToForum'),
7271
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
7272
                            ['class' => 'btn btn-primary']
7273
                        );
7274
                        break;
7275
                    case TOOL_QUIZ:
7276
                        if (!empty($row['path'])) {
7277
                            $exercise = new Exercise();
7278
                            $exercise->read($row['path']);
7279
                            $return .= $exercise->description.'<br />';
7280
                            $return .= Display::url(
7281
                                get_lang('GoToExercise'),
7282
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
7283
                                ['class' => 'btn btn-primary']
7284
                            );
7285
                        }
7286
                        break;
7287
                    case TOOL_LP_FINAL_ITEM:
7288
                        $return .= $this->getSavedFinalItem();
7289
                        break;
7290
                    case TOOL_DOCUMENT:
7291
                    case TOOL_READOUT_TEXT:
7292
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7293
                        $sql_doc = "SELECT path FROM $tbl_doc
7294
                                    WHERE c_id = $course_id AND iid = ".intval($row['path']);
7295
                        $result = Database::query($sql_doc);
7296
                        $path_file = Database::result($result, 0, 0);
7297
                        $path_parts = pathinfo($path_file);
7298
                        // TODO: Correct the following naive comparisons.
7299
                        if (in_array($path_parts['extension'], [
7300
                            'html',
7301
                            'txt',
7302
                            'png',
7303
                            'jpg',
7304
                            'JPG',
7305
                            'jpeg',
7306
                            'JPEG',
7307
                            'gif',
7308
                            'swf',
7309
                            'pdf',
7310
                            'htm',
7311
                        ])) {
7312
                            $return .= $this->display_document($row['path'], true, true);
7313
                        }
7314
                        break;
7315
                    case TOOL_HOTPOTATOES:
7316
                        $return .= $this->display_document($row['path'], false, true);
7317
                        break;
7318
                }
7319
                $return .= '</div>';
7320
            }
7321
        }
7322
7323
        return $return;
7324
    }
7325
7326
    /**
7327
     * Shows the needed forms for editing a specific item.
7328
     *
7329
     * @param int $item_id
7330
     *
7331
     * @throws Exception
7332
     * @throws HTML_QuickForm_Error
7333
     *
7334
     * @return string
7335
     */
7336
    public function display_edit_item(
7337
        $item_id,
7338
        $excludeExtraFields = []
7339
    ) {
7340
        $course_id = api_get_course_int_id();
7341
        $return = '';
7342
        $item_id = (int) $item_id;
7343
7344
        if (empty($item_id)) {
7345
            return '';
7346
        }
7347
7348
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7349
        $sql = "SELECT * FROM $tbl_lp_item
7350
                WHERE iid = ".$item_id;
7351
        $res = Database::query($sql);
7352
        $row = Database::fetch_array($res);
7353
        switch ($row['item_type']) {
7354
            case 'dir':
7355
            case 'asset':
7356
            case 'sco':
7357
                if (isset($_GET['view']) && $_GET['view'] == 'build') {
7358
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7359
                    $return .= $this->display_item_form(
7360
                        $row['item_type'],
7361
                        get_lang('EditCurrentChapter').' :',
7362
                        'edit',
7363
                        $item_id,
7364
                        $row
7365
                    );
7366
                } else {
7367
                    $return .= $this->display_item_form(
7368
                        $row['item_type'],
7369
                        get_lang('EditCurrentChapter').' :',
7370
                        'edit_item',
7371
                        $item_id,
7372
                        $row
7373
                    );
7374
                }
7375
                break;
7376
            case TOOL_DOCUMENT:
7377
            case TOOL_READOUT_TEXT:
7378
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7379
                $sql = "SELECT lp.*, doc.path as dir
7380
                        FROM $tbl_lp_item as lp
7381
                        LEFT JOIN $tbl_doc as doc
7382
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7383
                        WHERE
7384
                            doc.c_id = $course_id AND
7385
                            lp.iid = ".$item_id;
7386
                $res_step = Database::query($sql);
7387
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7388
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7389
7390
                if ($row['item_type'] === TOOL_DOCUMENT) {
7391
                    $return .= $this->display_document_form(
7392
                        'edit',
7393
                        $item_id,
7394
                        $row_step,
7395
                        null,
7396
                        $excludeExtraFields
7397
                    );
7398
                }
7399
7400
                if ($row['item_type'] === TOOL_READOUT_TEXT) {
7401
                    $return .= $this->displayFrmReadOutText('edit', $item_id, $row_step);
7402
                }
7403
                break;
7404
            case TOOL_LINK:
7405
                $linkId = (int) $row['path'];
7406
                if (!empty($linkId)) {
7407
                    $table = Database::get_course_table(TABLE_LINK);
7408
                    $sql = 'SELECT url FROM '.$table.'
7409
                            WHERE c_id = '.$course_id.' AND iid = '.$linkId;
7410
                    $res_link = Database::query($sql);
7411
                    $row_link = Database::fetch_array($res_link);
7412
                    if (empty($row_link)) {
7413
                        // Try with id
7414
                        $sql = 'SELECT url FROM '.$table.'
7415
                                WHERE c_id = '.$course_id.' AND id = '.$linkId;
7416
                        $res_link = Database::query($sql);
7417
                        $row_link = Database::fetch_array($res_link);
7418
                    }
7419
7420
                    if (is_array($row_link)) {
7421
                        $row['url'] = $row_link['url'];
7422
                    }
7423
                }
7424
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7425
                $return .= $this->display_link_form('edit', $item_id, $row, null, $excludeExtraFields);
7426
                break;
7427
            case TOOL_LP_FINAL_ITEM:
7428
                Session::write('finalItem', true);
7429
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7430
                $sql = "SELECT lp.*, doc.path as dir
7431
                        FROM $tbl_lp_item as lp
7432
                        LEFT JOIN $tbl_doc as doc
7433
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7434
                        WHERE
7435
                            doc.c_id = $course_id AND
7436
                            lp.iid = ".$item_id;
7437
                $res_step = Database::query($sql);
7438
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7439
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7440
                $return .= $this->display_document_form(
7441
                    'edit',
7442
                    $item_id,
7443
                    $row_step,
7444
                    null,
7445
                    $excludeExtraFields
7446
                );
7447
                break;
7448
            case TOOL_QUIZ:
7449
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7450
                $return .= $this->display_quiz_form('edit', $item_id, $row, $excludeExtraFields);
7451
                break;
7452
            case TOOL_HOTPOTATOES:
7453
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7454
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7455
                break;
7456
            case TOOL_STUDENTPUBLICATION:
7457
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7458
                $return .= $this->display_student_publication_form('edit', $item_id, $row, null, $excludeExtraFields);
7459
                break;
7460
            case TOOL_FORUM:
7461
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7462
                $return .= $this->display_forum_form('edit', $item_id, $row, $excludeExtraFields);
7463
                break;
7464
            case TOOL_THREAD:
7465
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7466
                $return .= $this->display_thread_form('edit', $item_id, $row);
7467
                break;
7468
        }
7469
7470
        return $return;
7471
    }
7472
7473
    /**
7474
     * Function that displays a list with al the resources that
7475
     * could be added to the learning path.
7476
     *
7477
     * @throws Exception
7478
     * @throws HTML_QuickForm_Error
7479
     *
7480
     * @return bool
7481
     */
7482
    public function display_resources()
7483
    {
7484
        $course_code = api_get_course_id();
7485
7486
        // Get all the docs.
7487
        $documents = $this->get_documents(true);
7488
7489
        // Get all the exercises.
7490
        $exercises = $this->get_exercises();
7491
7492
        // Get all the links.
7493
        $links = $this->get_links();
7494
7495
        // Get all the student publications.
7496
        $works = $this->get_student_publications();
7497
7498
        // Get all the forums.
7499
        $forums = $this->get_forums(null, $course_code);
7500
7501
        // Get the final item form (see BT#11048) .
7502
        $finish = $this->getFinalItemForm();
7503
7504
        $headers = [
7505
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7506
            Display::return_icon('quiz.png', get_lang('Quiz'), [], ICON_SIZE_BIG),
7507
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7508
            Display::return_icon('works.png', get_lang('Works'), [], ICON_SIZE_BIG),
7509
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7510
            Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), [], ICON_SIZE_BIG),
7511
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7512
        ];
7513
7514
        echo Display::return_message(get_lang('ClickOnTheLearnerViewToSeeYourLearningPath'), 'normal');
7515
        $dir = $this->display_item_form('dir', get_lang('EnterDataNewChapter'), 'add_item');
7516
7517
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
7518
7519
        echo Display::tabs(
7520
            $headers,
7521
            [
7522
                $documents,
7523
                $exercises,
7524
                $links,
7525
                $works,
7526
                $forums,
7527
                $dir,
7528
                $finish,
7529
            ],
7530
            'resource_tab',
7531
            [],
7532
            [],
7533
            $selected
7534
        );
7535
7536
        return true;
7537
    }
7538
7539
    /**
7540
     * Returns the extension of a document.
7541
     *
7542
     * @param string $filename
7543
     *
7544
     * @return string Extension (part after the last dot)
7545
     */
7546
    public function get_extension($filename)
7547
    {
7548
        $explode = explode('.', $filename);
7549
7550
        return $explode[count($explode) - 1];
7551
    }
7552
7553
    /**
7554
     * Displays a document by id.
7555
     *
7556
     * @param int  $id
7557
     * @param bool $show_title
7558
     * @param bool $iframe
7559
     * @param bool $edit_link
7560
     *
7561
     * @return string
7562
     */
7563
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7564
    {
7565
        $_course = api_get_course_info();
7566
        $course_id = api_get_course_int_id();
7567
        $id = (int) $id;
7568
        $return = '';
7569
        $table = Database::get_course_table(TABLE_DOCUMENT);
7570
        $sql_doc = "SELECT * FROM $table
7571
                    WHERE c_id = $course_id AND iid = $id";
7572
        $res_doc = Database::query($sql_doc);
7573
        $row_doc = Database::fetch_array($res_doc);
7574
7575
        // TODO: Add a path filter.
7576
        if ($iframe) {
7577
            $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>';
7578
        } else {
7579
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7580
        }
7581
7582
        return $return;
7583
    }
7584
7585
    /**
7586
     * Return HTML form to add/edit a quiz.
7587
     *
7588
     * @param string $action     Action (add/edit)
7589
     * @param int    $id         Item ID if already exists
7590
     * @param mixed  $extra_info Extra information (quiz ID if integer)
7591
     *
7592
     * @throws Exception
7593
     *
7594
     * @return string HTML form
7595
     */
7596
    public function display_quiz_form(
7597
        $action = 'add',
7598
        $id = 0,
7599
        $extra_info = '',
7600
        $excludeExtraFields = []
7601
    ) {
7602
        $course_id = api_get_course_int_id();
7603
        $id = (int) $id;
7604
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7605
7606
        if ($id != 0 && is_array($extra_info)) {
7607
            $item_title = $extra_info['title'];
7608
            $item_description = $extra_info['description'];
7609
        } elseif (is_numeric($extra_info)) {
7610
            $sql = "SELECT title, description
7611
                    FROM $tbl_quiz
7612
                    WHERE c_id = $course_id AND iid = ".$extra_info;
7613
7614
            $result = Database::query($sql);
7615
            $row = Database::fetch_array($result);
7616
            $item_title = $row['title'];
7617
            $item_description = $row['description'];
7618
        } else {
7619
            $item_title = '';
7620
            $item_description = '';
7621
        }
7622
        $item_title = Security::remove_XSS($item_title);
7623
        $item_description = Security::remove_XSS($item_description);
7624
7625
        $parent = 0;
7626
        if ($id != 0 && is_array($extra_info)) {
7627
            $parent = $extra_info['parent_item_id'];
7628
        }
7629
7630
        $arrLP = $this->getItemsForForm();
7631
        $this->tree_array($arrLP);
7632
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7633
        unset($this->arrMenu);
7634
7635
        $form = new FormValidator(
7636
            'quiz_form',
7637
            'POST',
7638
            $this->getCurrentBuildingModeURL()
7639
        );
7640
        $defaults = [];
7641
7642
        if ($action === 'add') {
7643
            $legend = get_lang('CreateTheExercise');
7644
        } elseif ($action === 'move') {
7645
            $legend = get_lang('MoveTheCurrentExercise');
7646
        } else {
7647
            $legend = get_lang('EditCurrentExecice');
7648
        }
7649
7650
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7651
            $legend .= Display::return_message(get_lang('Warning').' ! '.get_lang('WarningEditingDocument'));
7652
        }
7653
7654
        $form->addHeader($legend);
7655
7656
        if ($action != 'move') {
7657
            $this->setItemTitle($form);
7658
            $defaults['title'] = $item_title;
7659
        }
7660
7661
        // Select for Parent item, root or chapter
7662
        $selectParent = $form->addSelect(
7663
            'parent',
7664
            get_lang('Parent'),
7665
            [],
7666
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7667
        );
7668
        $selectParent->addOption($this->name, 0);
7669
7670
        $arrHide = [
7671
            $id,
7672
        ];
7673
        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...
7674
            if ($action != 'add') {
7675
                if (
7676
                    ($arrLP[$i]['item_type'] == 'dir') &&
7677
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7678
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7679
                ) {
7680
                    $selectParent->addOption(
7681
                        $arrLP[$i]['title'],
7682
                        $arrLP[$i]['id'],
7683
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7684
                    );
7685
7686
                    if ($parent == $arrLP[$i]['id']) {
7687
                        $selectParent->setSelected($arrLP[$i]['id']);
7688
                    }
7689
                } else {
7690
                    $arrHide[] = $arrLP[$i]['id'];
7691
                }
7692
            } else {
7693
                if ($arrLP[$i]['item_type'] == 'dir') {
7694
                    $selectParent->addOption(
7695
                        $arrLP[$i]['title'],
7696
                        $arrLP[$i]['id'],
7697
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7698
                    );
7699
7700
                    if ($parent == $arrLP[$i]['id']) {
7701
                        $selectParent->setSelected($arrLP[$i]['id']);
7702
                    }
7703
                }
7704
            }
7705
        }
7706
7707
        if (is_array($arrLP)) {
7708
            reset($arrLP);
7709
        }
7710
7711
        $selectPrevious = $form->addSelect(
7712
            'previous',
7713
            get_lang('Position'),
7714
            [],
7715
            ['id' => 'previous']
7716
        );
7717
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7718
7719
        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...
7720
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7721
                $arrLP[$i]['id'] != $id
7722
            ) {
7723
                $selectPrevious->addOption(
7724
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7725
                    $arrLP[$i]['id']
7726
                );
7727
7728
                if (is_array($extra_info)) {
7729
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7730
                        $selectPrevious->setSelected($arrLP[$i]['id']);
7731
                    }
7732
                } elseif ($action == 'add') {
7733
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7734
                }
7735
            }
7736
        }
7737
7738
        if ($action != 'move') {
7739
            $arrHide = [];
7740
            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...
7741
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7742
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7743
                }
7744
            }
7745
        }
7746
7747
        if ('edit' === $action) {
7748
            $extraField = new ExtraField('lp_item');
7749
            $extraField->addElements($form, $id, $excludeExtraFields);
7750
        }
7751
7752
        if ($action === 'add') {
7753
            $form->addButtonSave(get_lang('AddExercise'), 'submit_button');
7754
        } else {
7755
            $form->addButtonSave(get_lang('EditCurrentExecice'), 'submit_button');
7756
        }
7757
7758
        if ($action === 'move') {
7759
            $form->addHidden('title', $item_title);
7760
            $form->addHidden('description', $item_description);
7761
        }
7762
7763
        if (is_numeric($extra_info)) {
7764
            $form->addHidden('path', $extra_info);
7765
        } elseif (is_array($extra_info)) {
7766
            $form->addHidden('path', $extra_info['path']);
7767
        }
7768
7769
        $form->addHidden('type', TOOL_QUIZ);
7770
        $form->addHidden('post_time', time());
7771
        $form->setDefaults($defaults);
7772
7773
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7774
    }
7775
7776
    /**
7777
     * Addition of Hotpotatoes tests.
7778
     *
7779
     * @param string $action
7780
     * @param int    $id         Internal ID of the item
7781
     * @param string $extra_info
7782
     *
7783
     * @return string HTML structure to display the hotpotatoes addition formular
7784
     */
7785
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
7786
    {
7787
        $course_id = api_get_course_int_id();
7788
        $uploadPath = DIR_HOTPOTATOES;
7789
7790
        if ($id != 0 && is_array($extra_info)) {
7791
            $item_title = stripslashes($extra_info['title']);
7792
            $item_description = stripslashes($extra_info['description']);
7793
        } elseif (is_numeric($extra_info)) {
7794
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
7795
7796
            $sql = "SELECT * FROM $TBL_DOCUMENT
7797
                    WHERE
7798
                        c_id = $course_id AND
7799
                        path LIKE '".$uploadPath."/%/%htm%' AND
7800
                        iid = ".(int) $extra_info."
7801
                    ORDER BY iid ASC";
7802
7803
            $res_hot = Database::query($sql);
7804
            $row = Database::fetch_array($res_hot);
7805
7806
            $item_title = $row['title'];
7807
            $item_description = $row['description'];
7808
7809
            if (!empty($row['comment'])) {
7810
                $item_title = $row['comment'];
7811
            }
7812
        } else {
7813
            $item_title = '';
7814
            $item_description = '';
7815
        }
7816
7817
        $parent = 0;
7818
        if ($id != 0 && is_array($extra_info)) {
7819
            $parent = $extra_info['parent_item_id'];
7820
        }
7821
7822
        $arrLP = $this->getItemsForForm();
7823
        $legend = '<legend>';
7824
        if ($action == 'add') {
7825
            $legend .= get_lang('CreateTheExercise');
7826
        } elseif ($action == 'move') {
7827
            $legend .= get_lang('MoveTheCurrentExercise');
7828
        } else {
7829
            $legend .= get_lang('EditCurrentExecice');
7830
        }
7831
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7832
            $legend .= Display:: return_message(
7833
                get_lang('Warning').' ! '.get_lang('WarningEditingDocument')
7834
            );
7835
        }
7836
        $legend .= '</legend>';
7837
7838
        $return = '<form method="POST">';
7839
        $return .= $legend;
7840
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
7841
        $return .= '<tr>';
7842
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
7843
        $return .= '<td class="input">';
7844
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
7845
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
7846
        $arrHide = [$id];
7847
7848
        if (count($arrLP) > 0) {
7849
            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...
7850
                if ($action != 'add') {
7851
                    if ($arrLP[$i]['item_type'] == 'dir' &&
7852
                        !in_array($arrLP[$i]['id'], $arrHide) &&
7853
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7854
                    ) {
7855
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7856
                    } else {
7857
                        $arrHide[] = $arrLP[$i]['id'];
7858
                    }
7859
                } else {
7860
                    if ($arrLP[$i]['item_type'] == 'dir') {
7861
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7862
                    }
7863
                }
7864
            }
7865
            reset($arrLP);
7866
        }
7867
7868
        $return .= '</select>';
7869
        $return .= '</td>';
7870
        $return .= '</tr>';
7871
        $return .= '<tr>';
7872
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
7873
        $return .= '<td class="input">';
7874
        $return .= '<select id="previous" name="previous" size="1">';
7875
        $return .= '<option class="top" value="0">'.get_lang('FirstPosition').'</option>';
7876
7877
        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...
7878
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7879
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7880
                    $selected = 'selected="selected" ';
7881
                } elseif ($action == 'add') {
7882
                    $selected = 'selected="selected" ';
7883
                } else {
7884
                    $selected = '';
7885
                }
7886
7887
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.
7888
                    get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
7889
            }
7890
        }
7891
7892
        $return .= '</select>';
7893
        $return .= '</td>';
7894
        $return .= '</tr>';
7895
7896
        if ($action != 'move') {
7897
            $return .= '<tr>';
7898
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
7899
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
7900
            $return .= '</tr>';
7901
            $id_prerequisite = 0;
7902
            if (is_array($arrLP) && count($arrLP) > 0) {
7903
                foreach ($arrLP as $key => $value) {
7904
                    if ($value['id'] == $id) {
7905
                        $id_prerequisite = $value['prerequisite'];
7906
                        break;
7907
                    }
7908
                }
7909
7910
                $arrHide = [];
7911
                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...
7912
                    if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7913
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7914
                    }
7915
                }
7916
            }
7917
        }
7918
7919
        $return .= '<tr>';
7920
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.
7921
            get_lang('SaveHotpotatoes').'</button></td>';
7922
        $return .= '</tr>';
7923
        $return .= '</table>';
7924
7925
        if ($action == 'move') {
7926
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
7927
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
7928
        }
7929
7930
        if (is_numeric($extra_info)) {
7931
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
7932
        } elseif (is_array($extra_info)) {
7933
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
7934
        }
7935
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
7936
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
7937
        $return .= '</form>';
7938
7939
        return $return;
7940
    }
7941
7942
    /**
7943
     * Return the form to display the forum edit/add option.
7944
     *
7945
     * @param string $action
7946
     * @param int    $id         ID of the lp_item if already exists
7947
     * @param string $extra_info
7948
     *
7949
     * @throws Exception
7950
     *
7951
     * @return string HTML form
7952
     */
7953
    public function display_forum_form(
7954
        $action = 'add',
7955
        $id = 0,
7956
        $extra_info = '',
7957
        $excludeExtraFields = []
7958
    ) {
7959
        $course_id = api_get_course_int_id();
7960
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
7961
7962
        $item_title = '';
7963
        $item_description = '';
7964
7965
        if ($id != 0 && is_array($extra_info)) {
7966
            $item_title = stripslashes($extra_info['title']);
7967
        } elseif (is_numeric($extra_info)) {
7968
            $sql = "SELECT forum_title as title, forum_comment as comment
7969
                    FROM $tbl_forum
7970
                    WHERE c_id = $course_id AND forum_id = ".$extra_info;
7971
7972
            $result = Database::query($sql);
7973
            $row = Database::fetch_array($result);
7974
7975
            $item_title = $row['title'];
7976
            $item_description = $row['comment'];
7977
        }
7978
        $parent = 0;
7979
        if ($id != 0 && is_array($extra_info)) {
7980
            $parent = $extra_info['parent_item_id'];
7981
        }
7982
        $arrLP = $this->getItemsForForm();
7983
        $this->tree_array($arrLP);
7984
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7985
        unset($this->arrMenu);
7986
7987
        if ($action == 'add') {
7988
            $legend = get_lang('CreateTheForum');
7989
        } elseif ($action == 'move') {
7990
            $legend = get_lang('MoveTheCurrentForum');
7991
        } else {
7992
            $legend = get_lang('EditCurrentForum');
7993
        }
7994
7995
        $form = new FormValidator(
7996
            'forum_form',
7997
            'POST',
7998
            $this->getCurrentBuildingModeURL()
7999
        );
8000
        $defaults = [];
8001
8002
        $form->addHeader($legend);
8003
8004
        if ($action != 'move') {
8005
            $this->setItemTitle($form);
8006
            $defaults['title'] = $item_title;
8007
        }
8008
8009
        $selectParent = $form->addSelect(
8010
            'parent',
8011
            get_lang('Parent'),
8012
            [],
8013
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
8014
        );
8015
        $selectParent->addOption($this->name, 0);
8016
        $arrHide = [
8017
            $id,
8018
        ];
8019
        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...
8020
            if ($action != 'add') {
8021
                if ($arrLP[$i]['item_type'] == 'dir' &&
8022
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8023
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8024
                ) {
8025
                    $selectParent->addOption(
8026
                        $arrLP[$i]['title'],
8027
                        $arrLP[$i]['id'],
8028
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8029
                    );
8030
8031
                    if ($parent == $arrLP[$i]['id']) {
8032
                        $selectParent->setSelected($arrLP[$i]['id']);
8033
                    }
8034
                } else {
8035
                    $arrHide[] = $arrLP[$i]['id'];
8036
                }
8037
            } else {
8038
                if ($arrLP[$i]['item_type'] == 'dir') {
8039
                    $selectParent->addOption(
8040
                        $arrLP[$i]['title'],
8041
                        $arrLP[$i]['id'],
8042
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8043
                    );
8044
8045
                    if ($parent == $arrLP[$i]['id']) {
8046
                        $selectParent->setSelected($arrLP[$i]['id']);
8047
                    }
8048
                }
8049
            }
8050
        }
8051
8052
        if (is_array($arrLP)) {
8053
            reset($arrLP);
8054
        }
8055
8056
        $selectPrevious = $form->addSelect(
8057
            'previous',
8058
            get_lang('Position'),
8059
            [],
8060
            ['id' => 'previous', 'class' => 'learnpath_item_form']
8061
        );
8062
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8063
8064
        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...
8065
            if ($arrLP[$i]['parent_item_id'] == $parent &&
8066
                $arrLP[$i]['id'] != $id
8067
            ) {
8068
                $selectPrevious->addOption(
8069
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8070
                    $arrLP[$i]['id']
8071
                );
8072
8073
                if (isset($extra_info['previous_item_id']) &&
8074
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8075
                ) {
8076
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8077
                } elseif ($action == 'add') {
8078
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8079
                }
8080
            }
8081
        }
8082
8083
        if ($action != 'move') {
8084
            $id_prerequisite = 0;
8085
            if (is_array($arrLP)) {
8086
                foreach ($arrLP as $key => $value) {
8087
                    if ($value['id'] == $id) {
8088
                        $id_prerequisite = $value['prerequisite'];
8089
                        break;
8090
                    }
8091
                }
8092
            }
8093
8094
            $arrHide = [];
8095
            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...
8096
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8097
                    if (isset($extra_info['previous_item_id']) &&
8098
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
8099
                    ) {
8100
                        $s_selected_position = $arrLP[$i]['id'];
8101
                    } elseif ($action == 'add') {
8102
                        $s_selected_position = 0;
8103
                    }
8104
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8105
                }
8106
            }
8107
        }
8108
8109
        if ('edit' === $action) {
8110
            $extraField = new ExtraField('lp_item');
8111
            $extraField->addElements($form, $id, $excludeExtraFields);
8112
        }
8113
8114
        if ($action == 'add') {
8115
            $form->addButtonSave(get_lang('AddForumToCourse'), 'submit_button');
8116
        } else {
8117
            $form->addButtonSave(get_lang('EditCurrentForum'), 'submit_button');
8118
        }
8119
8120
        if ($action == 'move') {
8121
            $form->addHidden('title', $item_title);
8122
            $form->addHidden('description', $item_description);
8123
        }
8124
8125
        if (is_numeric($extra_info)) {
8126
            $form->addHidden('path', $extra_info);
8127
        } elseif (is_array($extra_info)) {
8128
            $form->addHidden('path', $extra_info['path']);
8129
        }
8130
        $form->addHidden('type', TOOL_FORUM);
8131
        $form->addHidden('post_time', time());
8132
        $form->setDefaults($defaults);
8133
8134
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
8135
    }
8136
8137
    /**
8138
     * Return HTML form to add/edit forum threads.
8139
     *
8140
     * @param string $action
8141
     * @param int    $id         Item ID if already exists in learning path
8142
     * @param string $extra_info
8143
     *
8144
     * @throws Exception
8145
     *
8146
     * @return string HTML form
8147
     */
8148
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
8149
    {
8150
        $course_id = api_get_course_int_id();
8151
        if (empty($course_id)) {
8152
            return null;
8153
        }
8154
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
8155
8156
        $item_title = '';
8157
        $item_description = '';
8158
        if ($id != 0 && is_array($extra_info)) {
8159
            $item_title = stripslashes($extra_info['title']);
8160
        } elseif (is_numeric($extra_info)) {
8161
            $sql = "SELECT thread_title as title FROM $tbl_forum
8162
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
8163
8164
            $result = Database::query($sql);
8165
            $row = Database::fetch_array($result);
8166
8167
            $item_title = $row['title'];
8168
            $item_description = '';
8169
        }
8170
8171
        $parent = 0;
8172
        if ($id != 0 && is_array($extra_info)) {
8173
            $parent = $extra_info['parent_item_id'];
8174
        }
8175
8176
        $arrLP = $this->getItemsForForm();
8177
        $this->tree_array($arrLP);
8178
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8179
        unset($this->arrMenu);
8180
8181
        $form = new FormValidator(
8182
            'thread_form',
8183
            'POST',
8184
            $this->getCurrentBuildingModeURL()
8185
        );
8186
        $defaults = [];
8187
8188
        if ($action == 'add') {
8189
            $legend = get_lang('CreateTheForum');
8190
        } elseif ($action == 'move') {
8191
            $legend = get_lang('MoveTheCurrentForum');
8192
        } else {
8193
            $legend = get_lang('EditCurrentForum');
8194
        }
8195
8196
        $form->addHeader($legend);
8197
        $selectParent = $form->addSelect(
8198
            'parent',
8199
            get_lang('Parent'),
8200
            [],
8201
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
8202
        );
8203
        $selectParent->addOption($this->name, 0);
8204
8205
        $arrHide = [
8206
            $id,
8207
        ];
8208
8209
        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...
8210
            if ($action != 'add') {
8211
                if (
8212
                    ($arrLP[$i]['item_type'] == 'dir') &&
8213
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8214
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8215
                ) {
8216
                    $selectParent->addOption(
8217
                        $arrLP[$i]['title'],
8218
                        $arrLP[$i]['id'],
8219
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8220
                    );
8221
8222
                    if ($parent == $arrLP[$i]['id']) {
8223
                        $selectParent->setSelected($arrLP[$i]['id']);
8224
                    }
8225
                } else {
8226
                    $arrHide[] = $arrLP[$i]['id'];
8227
                }
8228
            } else {
8229
                if ($arrLP[$i]['item_type'] == 'dir') {
8230
                    $selectParent->addOption(
8231
                        $arrLP[$i]['title'],
8232
                        $arrLP[$i]['id'],
8233
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8234
                    );
8235
8236
                    if ($parent == $arrLP[$i]['id']) {
8237
                        $selectParent->setSelected($arrLP[$i]['id']);
8238
                    }
8239
                }
8240
            }
8241
        }
8242
8243
        if ($arrLP != null) {
8244
            reset($arrLP);
8245
        }
8246
8247
        $selectPrevious = $form->addSelect(
8248
            'previous',
8249
            get_lang('Position'),
8250
            [],
8251
            ['id' => 'previous']
8252
        );
8253
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8254
8255
        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...
8256
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8257
                $selectPrevious->addOption(
8258
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8259
                    $arrLP[$i]['id']
8260
                );
8261
8262
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8263
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8264
                } elseif ($action == 'add') {
8265
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8266
                }
8267
            }
8268
        }
8269
8270
        if ($action != 'move') {
8271
            $this->setItemTitle($form);
8272
            $defaults['title'] = $item_title;
8273
8274
            $id_prerequisite = 0;
8275
            if ($arrLP != null) {
8276
                foreach ($arrLP as $key => $value) {
8277
                    if ($value['id'] == $id) {
8278
                        $id_prerequisite = $value['prerequisite'];
8279
                        break;
8280
                    }
8281
                }
8282
            }
8283
8284
            $arrHide = [];
8285
            $s_selected_position = 0;
8286
            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...
8287
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8288
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8289
                        $s_selected_position = $arrLP[$i]['id'];
8290
                    } elseif ($action == 'add') {
8291
                        $s_selected_position = 0;
8292
                    }
8293
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8294
                }
8295
            }
8296
8297
            $selectPrerequisites = $form->addSelect(
8298
                'prerequisites',
8299
                get_lang('LearnpathPrerequisites'),
8300
                [],
8301
                ['id' => 'prerequisites']
8302
            );
8303
            $selectPrerequisites->addOption(get_lang('NoPrerequisites'), 0);
8304
8305
            foreach ($arrHide as $key => $value) {
8306
                $selectPrerequisites->addOption($value['value'], $key);
8307
8308
                if ($key == $s_selected_position && $action == 'add') {
8309
                    $selectPrerequisites->setSelected($key);
8310
                } elseif ($key == $id_prerequisite && $action == 'edit') {
8311
                    $selectPrerequisites->setSelected($key);
8312
                }
8313
            }
8314
        }
8315
8316
        if ('edit' === $action) {
8317
            $extraField = new ExtraField('lp_item');
8318
            $extraField->addElements($form, $id);
8319
        }
8320
8321
        $form->addButtonSave(get_lang('Ok'), 'submit_button');
8322
8323
        if ($action == 'move') {
8324
            $form->addHidden('title', $item_title);
8325
            $form->addHidden('description', $item_description);
8326
        }
8327
8328
        if (is_numeric($extra_info)) {
8329
            $form->addHidden('path', $extra_info);
8330
        } elseif (is_array($extra_info)) {
8331
            $form->addHidden('path', $extra_info['path']);
8332
        }
8333
8334
        $form->addHidden('type', TOOL_THREAD);
8335
        $form->addHidden('post_time', time());
8336
        $form->setDefaults($defaults);
8337
8338
        return $form->returnForm();
8339
    }
8340
8341
    /**
8342
     * Return the HTML form to display an item (generally a dir item).
8343
     *
8344
     * @param string $item_type
8345
     * @param string $title
8346
     * @param string $action
8347
     * @param int    $id
8348
     * @param string $extra_info
8349
     *
8350
     * @throws Exception
8351
     * @throws HTML_QuickForm_Error
8352
     *
8353
     * @return string HTML form
8354
     */
8355
    public function display_item_form(
8356
        $item_type,
8357
        $title = '',
8358
        $action = 'add_item',
8359
        $id = 0,
8360
        $extra_info = 'new'
8361
    ) {
8362
        $_course = api_get_course_info();
8363
8364
        global $charset;
8365
8366
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8367
        $item_title = '';
8368
        $item_description = '';
8369
        $item_path_fck = '';
8370
8371
        $parent = 0;
8372
        $previousId = null;
8373
        if ($id != 0 && is_array($extra_info)) {
8374
            $item_title = $extra_info['title'];
8375
            $item_description = $extra_info['description'];
8376
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8377
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8378
            $parent = $extra_info['parent_item_id'];
8379
            $previousId = $extra_info['previous_item_id'];
8380
        }
8381
8382
        if ($extra_info instanceof learnpathItem) {
8383
            $item_title = $extra_info->get_title();
8384
            $item_description = $extra_info->get_description();
8385
            $path = $extra_info->get_path();
8386
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($path);
8387
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($path);
8388
            $parent = $extra_info->get_parent();
8389
            $previousId = $extra_info->previous;
8390
        }
8391
8392
        $id = (int) $id;
8393
        $sql = "SELECT * FROM $tbl_lp_item
8394
                WHERE
8395
                    lp_id = ".$this->lp_id." AND
8396
                    iid != $id";
8397
8398
        if ($item_type === 'dir') {
8399
            $sql .= " AND parent_item_id = 0";
8400
        }
8401
8402
        $result = Database::query($sql);
8403
        $arrLP = [];
8404
        while ($row = Database::fetch_array($result)) {
8405
            $arrLP[] = [
8406
                'id' => $row['iid'],
8407
                'item_type' => $row['item_type'],
8408
                'title' => $this->cleanItemTitle($row['title']),
8409
                'title_raw' => $row['title'],
8410
                'path' => $row['path'],
8411
                'description' => $row['description'],
8412
                'parent_item_id' => $row['parent_item_id'],
8413
                'previous_item_id' => $row['previous_item_id'],
8414
                'next_item_id' => $row['next_item_id'],
8415
                'max_score' => $row['max_score'],
8416
                'min_score' => $row['min_score'],
8417
                'mastery_score' => $row['mastery_score'],
8418
                'prerequisite' => $row['prerequisite'],
8419
                'display_order' => $row['display_order'],
8420
            ];
8421
        }
8422
8423
        $this->tree_array($arrLP);
8424
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8425
        unset($this->arrMenu);
8426
8427
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8428
8429
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
8430
        $defaults['title'] = api_html_entity_decode(
8431
            $item_title,
8432
            ENT_QUOTES,
8433
            $charset
8434
        );
8435
        $defaults['description'] = $item_description;
8436
8437
        $form->addHeader($title);
8438
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
8439
        $arrHide[0]['padding'] = 20;
8440
        $charset = api_get_system_encoding();
8441
        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...
8442
            if ($action != 'add') {
8443
                if ($arrLP[$i]['item_type'] === 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8444
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8445
                ) {
8446
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8447
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8448
                    if ($parent == $arrLP[$i]['id']) {
8449
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8450
                    }
8451
                }
8452
            } else {
8453
                if ($arrLP[$i]['item_type'] === 'dir') {
8454
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8455
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8456
                    if ($parent == $arrLP[$i]['id']) {
8457
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8458
                    }
8459
                }
8460
            }
8461
        }
8462
8463
        if ($action !== 'move') {
8464
            $this->setItemTitle($form);
8465
        } else {
8466
            $form->addElement('hidden', 'title');
8467
        }
8468
8469
        $parentSelect = $form->addElement(
8470
            'select',
8471
            'parent',
8472
            get_lang('Parent'),
8473
            '',
8474
            [
8475
                'id' => 'idParent',
8476
                'onchange' => 'javascript: load_cbo(this.value);',
8477
            ]
8478
        );
8479
8480
        foreach ($arrHide as $key => $value) {
8481
            $parentSelect->addOption(
8482
                $value['value'],
8483
                $key,
8484
                'style="padding-left:'.$value['padding'].'px;"'
8485
            );
8486
            $lastPosition = $key;
8487
        }
8488
8489
        if (!empty($s_selected_parent)) {
8490
            $parentSelect->setSelected($s_selected_parent);
8491
        }
8492
8493
        if (is_array($arrLP)) {
8494
            reset($arrLP);
8495
        }
8496
8497
        $arrHide = [];
8498
        // POSITION
8499
        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...
8500
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id &&
8501
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
8502
                //this is the same!
8503
                if (isset($previousId) && $previousId == $arrLP[$i]['id']) {
8504
                    $s_selected_position = $arrLP[$i]['id'];
8505
                } elseif ($action === 'add') {
8506
                    $s_selected_position = $arrLP[$i]['id'];
8507
                }
8508
8509
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8510
            }
8511
        }
8512
8513
        $position = $form->addElement(
8514
            'select',
8515
            'previous',
8516
            get_lang('Position'),
8517
            '',
8518
            ['id' => 'previous']
8519
        );
8520
        $padding = isset($value['padding']) ? $value['padding'] : 0;
8521
        $position->addOption(get_lang('FirstPosition'), 0, 'style="padding-left:'.$padding.'px;"');
8522
8523
        $lastPosition = null;
8524
        foreach ($arrHide as $key => $value) {
8525
            $position->addOption($value['value'], $key, 'style="padding-left:'.$padding.'px;"');
8526
            $lastPosition = $key;
8527
        }
8528
8529
        if (!empty($s_selected_position)) {
8530
            $position->setSelected($s_selected_position);
8531
        }
8532
8533
        // When new chapter add at the end
8534
        if ($action === 'add_item') {
8535
            $position->setSelected($lastPosition);
8536
        }
8537
8538
        if (is_array($arrLP)) {
8539
            reset($arrLP);
8540
        }
8541
8542
        $form->addButtonSave(get_lang('SaveSection'), 'submit_button');
8543
8544
        //fix in order to use the tab
8545
        if ($item_type === 'dir') {
8546
            $form->addElement('hidden', 'type', 'dir');
8547
        }
8548
8549
        $extension = null;
8550
        if (!empty($item_path)) {
8551
            $extension = pathinfo($item_path, PATHINFO_EXTENSION);
8552
        }
8553
8554
        //assets can't be modified
8555
        //$item_type == 'asset' ||
8556
        if (($item_type === 'sco') && ($extension === 'html' || $extension === 'htm')) {
8557
            if ($item_type === 'sco') {
8558
                $form->addElement(
8559
                    'html',
8560
                    '<script>alert("'.get_lang('WarningWhenEditingScorm').'")</script>'
8561
                );
8562
            }
8563
            $renderer = $form->defaultRenderer();
8564
            $renderer->setElementTemplate(
8565
                '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
8566
                'content_lp'
8567
            );
8568
8569
            $relative_prefix = '';
8570
            $editor_config = [
8571
                'ToolbarSet' => 'LearningPathDocuments',
8572
                'Width' => '100%',
8573
                'Height' => '500',
8574
                'FullPage' => true,
8575
                'CreateDocumentDir' => $relative_prefix,
8576
                'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
8577
                'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
8578
            ];
8579
8580
            $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
8581
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
8582
            $defaults['content_lp'] = file_get_contents($content_path);
8583
        }
8584
8585
        if (!empty($id)) {
8586
            $form->addHidden('id', $id);
8587
        }
8588
8589
        $form->addElement('hidden', 'type', $item_type);
8590
        $form->addElement('hidden', 'post_time', time());
8591
        $form->setDefaults($defaults);
8592
8593
        return $form->returnForm();
8594
    }
8595
8596
    /**
8597
     * @return string
8598
     */
8599
    public function getCurrentBuildingModeURL()
8600
    {
8601
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
8602
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
8603
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
8604
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
8605
8606
        $currentUrl = api_get_self().'?'.api_get_cidreq().
8607
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
8608
8609
        return $currentUrl;
8610
    }
8611
8612
    /**
8613
     * Returns the form to update or create a document.
8614
     *
8615
     * @param string        $action     (add/edit)
8616
     * @param int           $id         ID of the lp_item (if already exists)
8617
     * @param mixed         $extra_info Integer if document ID, string if info ('new')
8618
     * @param learnpathItem $item
8619
     *
8620
     * @return string HTML form
8621
     */
8622
    public function display_document_form(
8623
        $action = 'add',
8624
        $id = 0,
8625
        $extra_info = 'new',
8626
        $item = null,
8627
        $excludeExtraFields = []
8628
    ) {
8629
        $course_id = api_get_course_int_id();
8630
        $_course = api_get_course_info();
8631
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8632
8633
        $no_display_edit_textarea = false;
8634
        //If action==edit document
8635
        //We don't display the document form if it's not an editable document (html or txt file)
8636
        if ($action === 'edit') {
8637
            if (is_array($extra_info)) {
8638
                $path_parts = pathinfo($extra_info['dir']);
8639
                if ($path_parts['extension'] !== 'txt' && $path_parts['extension'] !== 'html') {
8640
                    $no_display_edit_textarea = true;
8641
                }
8642
            }
8643
        }
8644
        $no_display_add = false;
8645
        // If action==add an existing document
8646
        // We don't display the document form if it's not an editable document (html or txt file).
8647
        if ($action === 'add') {
8648
            if (is_numeric($extra_info)) {
8649
                $extra_info = (int) $extra_info;
8650
                $sql_doc = "SELECT path FROM $tbl_doc
8651
                            WHERE c_id = $course_id AND iid = ".$extra_info;
8652
                $result = Database::query($sql_doc);
8653
                $path_file = Database::result($result, 0, 0);
8654
                $path_parts = pathinfo($path_file);
8655
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8656
                    $no_display_add = true;
8657
                }
8658
            }
8659
        }
8660
8661
        $item_title = '';
8662
        $item_description = '';
8663
        if ($id != 0 && is_array($extra_info)) {
8664
            $item_title = stripslashes($extra_info['title']);
8665
            $item_description = stripslashes($extra_info['description']);
8666
            if (empty($item_title)) {
8667
                $path_parts = pathinfo($extra_info['path']);
8668
                $item_title = stripslashes($path_parts['filename']);
8669
            }
8670
        } elseif (is_numeric($extra_info)) {
8671
            $sql = "SELECT path, title FROM $tbl_doc
8672
                    WHERE
8673
                        c_id = ".$course_id." AND
8674
                        iid = ".intval($extra_info);
8675
            $result = Database::query($sql);
8676
            $row = Database::fetch_array($result);
8677
            $item_title = $row['title'];
8678
            $item_title = str_replace('_', ' ', $item_title);
8679
            if (empty($item_title)) {
8680
                $path_parts = pathinfo($row['path']);
8681
                $item_title = stripslashes($path_parts['filename']);
8682
            }
8683
        }
8684
8685
        $return = '<legend>';
8686
        $parent = 0;
8687
        if ($id != 0 && is_array($extra_info)) {
8688
            $parent = $extra_info['parent_item_id'];
8689
        }
8690
8691
        $selectedPosition = 0;
8692
        if (is_array($extra_info) && isset($extra_info['previous_item_id'])) {
8693
            $selectedPosition = $extra_info['previous_item_id'];
8694
        }
8695
8696
        if ($item instanceof learnpathItem) {
8697
            $item_title = stripslashes($item->get_title());
8698
            $item_description = stripslashes($item->get_description());
8699
            $selectedPosition = $item->previous;
8700
            $parent = $item->parent;
8701
        }
8702
8703
        $arrLP = $this->getItemsForForm();
8704
        $this->tree_array($arrLP);
8705
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8706
        unset($this->arrMenu);
8707
8708
        if ($action === 'add') {
8709
            $return .= get_lang('CreateTheDocument');
8710
        } elseif ($action === 'move') {
8711
            $return .= get_lang('MoveTheCurrentDocument');
8712
        } else {
8713
            $return .= get_lang('EditTheCurrentDocument');
8714
        }
8715
        $return .= '</legend>';
8716
8717
        if (isset($_GET['edit']) && $_GET['edit'] === 'true') {
8718
            $return .= Display::return_message(
8719
                '<strong>'.get_lang('Warning').' !</strong><br />'.get_lang('WarningEditingDocument'),
8720
                false
8721
            );
8722
        }
8723
        $form = new FormValidator(
8724
            'form',
8725
            'POST',
8726
            $this->getCurrentBuildingModeURL(),
8727
            '',
8728
            ['enctype' => 'multipart/form-data']
8729
        );
8730
        $defaults['title'] = Security::remove_XSS($item_title);
8731
        if (empty($item_title)) {
8732
            $defaults['title'] = Security::remove_XSS($item_title);
8733
        }
8734
        $defaults['description'] = $item_description;
8735
        $form->addElement('html', $return);
8736
8737
        if ($action !== 'move') {
8738
            $data = $this->generate_lp_folder($_course);
8739
            if ($action !== 'edit') {
8740
                $folders = DocumentManager::get_all_document_folders(
8741
                    $_course,
8742
                    0,
8743
                    true
8744
                );
8745
                DocumentManager::build_directory_selector(
8746
                    $folders,
8747
                    '',
8748
                    [],
8749
                    true,
8750
                    $form,
8751
                    'directory_parent_id'
8752
                );
8753
            }
8754
8755
            if (isset($data['id'])) {
8756
                $defaults['directory_parent_id'] = $data['id'];
8757
            }
8758
            $this->setItemTitle($form);
8759
        }
8760
8761
        $arrHide[0]['value'] = $this->name;
8762
        $arrHide[0]['padding'] = 20;
8763
8764
        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...
8765
            if ($action !== 'add') {
8766
                if ($arrLP[$i]['item_type'] === 'dir' &&
8767
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8768
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8769
                ) {
8770
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8771
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8772
                }
8773
            } else {
8774
                if ($arrLP[$i]['item_type'] == 'dir') {
8775
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8776
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8777
                }
8778
            }
8779
        }
8780
8781
        $parentSelect = $form->addSelect(
8782
            'parent',
8783
            get_lang('Parent'),
8784
            [],
8785
            [
8786
                'id' => 'idParent',
8787
                'onchange' => 'javascript: load_cbo(this.value);',
8788
            ]
8789
        );
8790
8791
        $my_count = 0;
8792
        foreach ($arrHide as $key => $value) {
8793
            if ($my_count != 0) {
8794
                // The LP name is also the first section and is not in the same charset like the other sections.
8795
                $value['value'] = Security::remove_XSS($value['value']);
8796
                $parentSelect->addOption(
8797
                    $value['value'],
8798
                    $key,
8799
                    'style="padding-left:'.$value['padding'].'px;"'
8800
                );
8801
            } else {
8802
                $value['value'] = Security::remove_XSS($value['value']);
8803
                $parentSelect->addOption(
8804
                    $value['value'],
8805
                    $key,
8806
                    'style="padding-left:'.$value['padding'].'px;"'
8807
                );
8808
            }
8809
            $my_count++;
8810
        }
8811
8812
        if (!empty($id)) {
8813
            $parentSelect->setSelected($parent);
8814
        } else {
8815
            $parent_item_id = Session::read('parent_item_id', 0);
8816
            $parentSelect->setSelected($parent_item_id);
8817
        }
8818
8819
        if (is_array($arrLP)) {
8820
            reset($arrLP);
8821
        }
8822
8823
        $arrHide = [];
8824
        // POSITION
8825
        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...
8826
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
8827
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
8828
            ) {
8829
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8830
            }
8831
        }
8832
8833
        $position = $form->addSelect(
8834
            'previous',
8835
            get_lang('Position'),
8836
            [],
8837
            ['id' => 'previous']
8838
        );
8839
8840
        $position->addOption(get_lang('FirstPosition'), 0);
8841
        foreach ($arrHide as $key => $value) {
8842
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8843
            $position->addOption(
8844
                $value['value'],
8845
                $key,
8846
                'style="padding-left:'.$padding.'px;"'
8847
            );
8848
        }
8849
        $position->setSelected($selectedPosition);
8850
8851
        if (is_array($arrLP)) {
8852
            reset($arrLP);
8853
        }
8854
8855
        if ('edit' === $action) {
8856
            $extraField = new ExtraField('lp_item');
8857
            $extraField->addElements($form, $id, $excludeExtraFields);
8858
        }
8859
8860
        if ($action !== 'move') {
8861
            $arrHide = [];
8862
            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...
8863
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] !== 'dir' &&
8864
                    $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8865
                ) {
8866
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8867
                }
8868
            }
8869
8870
            if (!$no_display_add) {
8871
                $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8872
                $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8873
                if ($extra_info === 'new' || $item_type == TOOL_DOCUMENT ||
8874
                    $item_type == TOOL_LP_FINAL_ITEM || $edit === 'true'
8875
                ) {
8876
                    if (isset($_POST['content'])) {
8877
                        $content = stripslashes($_POST['content']);
8878
                    } elseif (is_array($extra_info)) {
8879
                        //If it's an html document or a text file
8880
                        if (!$no_display_edit_textarea) {
8881
                            $content = $this->display_document(
8882
                                $extra_info['path'],
8883
                                false,
8884
                                false
8885
                            );
8886
                        }
8887
                    } elseif (is_numeric($extra_info)) {
8888
                        $content = $this->display_document(
8889
                            $extra_info,
8890
                            false,
8891
                            false
8892
                        );
8893
                    } else {
8894
                        $content = '';
8895
                    }
8896
8897
                    if (!$no_display_edit_textarea) {
8898
                        // We need to calculate here some specific settings for the online editor.
8899
                        // The calculated settings work for documents in the Documents tool
8900
                        // (on the root or in subfolders).
8901
                        // For documents in native scorm packages it is unclear whether the
8902
                        // online editor should be activated or not.
8903
8904
                        // A new document, it is in the root of the repository.
8905
                        if (is_array($extra_info) && $extra_info != 'new') {
8906
                            // The document already exists. Whe have to determine its relative path towards the repository root.
8907
                            $relative_path = explode('/', $extra_info['dir']);
8908
                            $cnt = count($relative_path) - 2;
8909
                            if ($cnt < 0) {
8910
                                $cnt = 0;
8911
                            }
8912
                            $relative_prefix = str_repeat('../', $cnt);
8913
                            $relative_path = array_slice($relative_path, 1, $cnt);
8914
                            $relative_path = implode('/', $relative_path);
8915
                            if (strlen($relative_path) > 0) {
8916
                                $relative_path = $relative_path.'/';
8917
                            }
8918
                        } else {
8919
                            $result = $this->generate_lp_folder($_course);
8920
                            $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
8921
                            $relative_prefix = '../../';
8922
                        }
8923
8924
                        $editor_config = [
8925
                            'ToolbarSet' => 'LearningPathDocuments',
8926
                            'Width' => '100%',
8927
                            'Height' => '500',
8928
                            'FullPage' => true,
8929
                            'CreateDocumentDir' => $relative_prefix,
8930
                            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
8931
                            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
8932
                        ];
8933
8934
                        if ($_GET['action'] === 'add_item') {
8935
                            $class = 'add';
8936
                            $text = get_lang('LPCreateDocument');
8937
                        } else {
8938
                            if ($_GET['action'] === 'edit_item') {
8939
                                $class = 'save';
8940
                                $text = get_lang('SaveDocument');
8941
                            }
8942
                        }
8943
8944
                        $form->addButtonSave($text, 'submit_button');
8945
                        $renderer = $form->defaultRenderer();
8946
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
8947
                        $form->addElement('html', '<div class="editor-lp">');
8948
                        $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
8949
                        $form->addElement('html', '</div>');
8950
                        $defaults['content_lp'] = $content;
8951
                    }
8952
                } elseif (is_numeric($extra_info)) {
8953
                    $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8954
8955
                    $return = $this->display_document($extra_info, true, true, true);
8956
                    $form->addElement('html', $return);
8957
                }
8958
            }
8959
        }
8960
        if (isset($extra_info['item_type']) &&
8961
            $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
8962
        ) {
8963
            $parentSelect->freeze();
8964
            $position->freeze();
8965
        }
8966
8967
        if ($action === 'move') {
8968
            $form->addElement('hidden', 'title', $item_title);
8969
            $form->addElement('hidden', 'description', $item_description);
8970
        }
8971
        if (is_numeric($extra_info)) {
8972
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8973
            $form->addElement('hidden', 'path', $extra_info);
8974
        } elseif (is_array($extra_info)) {
8975
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8976
            $form->addElement('hidden', 'path', $extra_info['path']);
8977
        }
8978
8979
        if ($item instanceof learnpathItem) {
8980
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8981
            $form->addElement('hidden', 'path', $item->path);
8982
        }
8983
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
8984
        $form->addElement('hidden', 'post_time', time());
8985
        $form->setDefaults($defaults);
8986
8987
        return $form->returnForm();
8988
    }
8989
8990
    /**
8991
     * Returns the form to update or create a read-out text.
8992
     *
8993
     * @param string $action     "add" or "edit"
8994
     * @param int    $id         ID of the lp_item (if already exists)
8995
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8996
     *
8997
     * @throws Exception
8998
     * @throws HTML_QuickForm_Error
8999
     *
9000
     * @return string HTML form
9001
     */
9002
    public function displayFrmReadOutText($action = 'add', $id = 0, $extra_info = 'new')
9003
    {
9004
        $course_id = api_get_course_int_id();
9005
        $_course = api_get_course_info();
9006
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
9007
9008
        $no_display_edit_textarea = false;
9009
        //If action==edit document
9010
        //We don't display the document form if it's not an editable document (html or txt file)
9011
        if ($action == 'edit') {
9012
            if (is_array($extra_info)) {
9013
                $path_parts = pathinfo($extra_info['dir']);
9014
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
9015
                    $no_display_edit_textarea = true;
9016
                }
9017
            }
9018
        }
9019
        $no_display_add = false;
9020
9021
        $item_title = '';
9022
        $item_description = '';
9023
        if ($id != 0 && is_array($extra_info)) {
9024
            $item_title = stripslashes($extra_info['title']);
9025
            $item_description = stripslashes($extra_info['description']);
9026
            $item_terms = stripslashes($extra_info['terms']);
9027
            if (empty($item_title)) {
9028
                $path_parts = pathinfo($extra_info['path']);
9029
                $item_title = stripslashes($path_parts['filename']);
9030
            }
9031
        } elseif (is_numeric($extra_info)) {
9032
            $sql = "SELECT path, title FROM $tbl_doc WHERE c_id = ".$course_id." AND iid = ".intval($extra_info);
9033
            $result = Database::query($sql);
9034
            $row = Database::fetch_array($result);
9035
            $item_title = $row['title'];
9036
            $item_title = str_replace('_', ' ', $item_title);
9037
            if (empty($item_title)) {
9038
                $path_parts = pathinfo($row['path']);
9039
                $item_title = stripslashes($path_parts['filename']);
9040
            }
9041
        }
9042
9043
        $parent = 0;
9044
        if ($id != 0 && is_array($extra_info)) {
9045
            $parent = $extra_info['parent_item_id'];
9046
        }
9047
9048
        $arrLP = $this->getItemsForForm();
9049
        $this->tree_array($arrLP);
9050
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9051
        unset($this->arrMenu);
9052
9053
        if ($action === 'add') {
9054
            $formHeader = get_lang('CreateTheDocument');
9055
        } else {
9056
            $formHeader = get_lang('EditTheCurrentDocument');
9057
        }
9058
9059
        if ('edit' === $action) {
9060
            $urlAudioIcon = Display::url(
9061
                Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY),
9062
                api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'&'
9063
                    .http_build_query(['view' => 'build', 'id' => $id, 'action' => 'add_audio'])
9064
            );
9065
        } else {
9066
            $urlAudioIcon = Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY);
9067
        }
9068
9069
        $form = new FormValidator(
9070
            'frm_add_reading',
9071
            'POST',
9072
            $this->getCurrentBuildingModeURL(),
9073
            '',
9074
            ['enctype' => 'multipart/form-data']
9075
        );
9076
        $form->addHeader($formHeader);
9077
        $form->addHtml(
9078
            Display::return_message(
9079
                sprintf(get_lang('FrmReadOutTextIntro'), $urlAudioIcon),
9080
                'normal',
9081
                false
9082
            )
9083
        );
9084
        $defaults['title'] = !empty($item_title) ? Security::remove_XSS($item_title) : '';
9085
        $defaults['description'] = $item_description;
9086
9087
        $data = $this->generate_lp_folder($_course);
9088
9089
        if ($action != 'edit') {
9090
            $folders = DocumentManager::get_all_document_folders($_course, 0, true);
9091
            DocumentManager::build_directory_selector(
9092
                $folders,
9093
                '',
9094
                [],
9095
                true,
9096
                $form,
9097
                'directory_parent_id'
9098
            );
9099
        }
9100
9101
        if (isset($data['id'])) {
9102
            $defaults['directory_parent_id'] = $data['id'];
9103
        }
9104
        $this->setItemTitle($form);
9105
9106
        $arrHide[0]['value'] = $this->name;
9107
        $arrHide[0]['padding'] = 20;
9108
9109
        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...
9110
            if ($action != 'add') {
9111
                if ($arrLP[$i]['item_type'] == 'dir' &&
9112
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9113
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9114
                ) {
9115
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9116
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9117
                }
9118
            } else {
9119
                if ($arrLP[$i]['item_type'] == 'dir') {
9120
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9121
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9122
                }
9123
            }
9124
        }
9125
9126
        $parent_select = $form->addSelect(
9127
            'parent',
9128
            get_lang('Parent'),
9129
            [],
9130
            ['onchange' => "javascript: load_cbo(this.value, 'frm_add_reading_previous');"]
9131
        );
9132
9133
        $my_count = 0;
9134
        foreach ($arrHide as $key => $value) {
9135
            if ($my_count != 0) {
9136
                // The LP name is also the first section and is not in the same charset like the other sections.
9137
                $value['value'] = Security::remove_XSS($value['value']);
9138
                $parent_select->addOption(
9139
                    $value['value'],
9140
                    $key,
9141
                    'style="padding-left:'.$value['padding'].'px;"'
9142
                );
9143
            } else {
9144
                $value['value'] = Security::remove_XSS($value['value']);
9145
                $parent_select->addOption(
9146
                    $value['value'],
9147
                    $key,
9148
                    'style="padding-left:'.$value['padding'].'px;"'
9149
                );
9150
            }
9151
            $my_count++;
9152
        }
9153
9154
        if (!empty($id)) {
9155
            $parent_select->setSelected($parent);
9156
        } else {
9157
            $parent_item_id = Session::read('parent_item_id', 0);
9158
            $parent_select->setSelected($parent_item_id);
9159
        }
9160
9161
        if (is_array($arrLP)) {
9162
            reset($arrLP);
9163
        }
9164
9165
        $arrHide = [];
9166
        $s_selected_position = null;
9167
9168
        // POSITION
9169
        $lastPosition = null;
9170
9171
        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...
9172
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) &&
9173
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9174
            ) {
9175
                if ((isset($extra_info['previous_item_id']) &&
9176
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
9177
                ) {
9178
                    $s_selected_position = $arrLP[$i]['id'];
9179
                }
9180
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
9181
            }
9182
            $lastPosition = $arrLP[$i]['id'];
9183
        }
9184
9185
        if (empty($s_selected_position)) {
9186
            $s_selected_position = $lastPosition;
9187
        }
9188
9189
        $position = $form->addSelect(
9190
            'previous',
9191
            get_lang('Position'),
9192
            []
9193
        );
9194
        $position->addOption(get_lang('FirstPosition'), 0);
9195
9196
        foreach ($arrHide as $key => $value) {
9197
            $padding = isset($value['padding']) ? $value['padding'] : 20;
9198
            $position->addOption(
9199
                $value['value'],
9200
                $key,
9201
                'style="padding-left:'.$padding.'px;"'
9202
            );
9203
        }
9204
        $position->setSelected($s_selected_position);
9205
9206
        if (is_array($arrLP)) {
9207
            reset($arrLP);
9208
        }
9209
9210
        if ('edit' === $action) {
9211
            $extraField = new ExtraField('lp_item');
9212
            $extraField->addElements($form, $id);
9213
        }
9214
9215
        $arrHide = [];
9216
9217
        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...
9218
            if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
9219
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9220
            ) {
9221
                $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9222
            }
9223
        }
9224
9225
        if (!$no_display_add) {
9226
            $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
9227
            $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
9228
9229
            if ($extra_info == 'new' || $item_type == TOOL_READOUT_TEXT || $edit == 'true') {
9230
                if (!$no_display_edit_textarea) {
9231
                    $content = '';
9232
9233
                    if (isset($_POST['content'])) {
9234
                        $content = stripslashes($_POST['content']);
9235
                    } elseif (is_array($extra_info)) {
9236
                        $content = $this->display_document($extra_info['path'], false, false);
9237
                    } elseif (is_numeric($extra_info)) {
9238
                        $content = $this->display_document($extra_info, false, false);
9239
                    }
9240
9241
                    // A new document, it is in the root of the repository.
9242
                    if (is_array($extra_info) && $extra_info != 'new') {
9243
                    } else {
9244
                        $this->generate_lp_folder($_course);
9245
                    }
9246
9247
                    if ($_GET['action'] == 'add_item') {
9248
                        $text = get_lang('LPCreateDocument');
9249
                    } else {
9250
                        $text = get_lang('SaveDocument');
9251
                    }
9252
9253
                    $form->addTextarea('content_lp', get_lang('Content'), ['rows' => 20]);
9254
                    $form
9255
                        ->defaultRenderer()
9256
                        ->setElementTemplate($form->getDefaultElementTemplate(), 'content_lp');
9257
                    $form->addButtonSave($text, 'submit_button');
9258
                    $defaults['content_lp'] = $content;
9259
                }
9260
            } elseif (is_numeric($extra_info)) {
9261
                $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9262
9263
                $return = $this->display_document($extra_info, true, true, true);
9264
                $form->addElement('html', $return);
9265
            }
9266
        }
9267
9268
        if (is_numeric($extra_info)) {
9269
            $form->addElement('hidden', 'path', $extra_info);
9270
        } elseif (is_array($extra_info)) {
9271
            $form->addElement('hidden', 'path', $extra_info['path']);
9272
        }
9273
9274
        $form->addElement('hidden', 'type', TOOL_READOUT_TEXT);
9275
        $form->addElement('hidden', 'post_time', time());
9276
        $form->setDefaults($defaults);
9277
9278
        return $form->returnForm();
9279
    }
9280
9281
    /**
9282
     * @param array  $courseInfo
9283
     * @param string $content
9284
     * @param string $title
9285
     * @param int    $parentId
9286
     *
9287
     * @throws \Doctrine\ORM\ORMException
9288
     * @throws \Doctrine\ORM\OptimisticLockException
9289
     * @throws \Doctrine\ORM\TransactionRequiredException
9290
     *
9291
     * @return int
9292
     */
9293
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
9294
    {
9295
        $creatorId = api_get_user_id();
9296
        $sessionId = api_get_session_id();
9297
9298
        // Generates folder
9299
        $result = $this->generate_lp_folder($courseInfo);
9300
        $dir = $result['dir'];
9301
9302
        if (empty($parentId) || $parentId == '/') {
9303
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
9304
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
9305
9306
            if ($parentId === '/') {
9307
                $dir = '/';
9308
            }
9309
9310
            // Please, do not modify this dirname formatting.
9311
            if (strstr($dir, '..')) {
9312
                $dir = '/';
9313
            }
9314
9315
            if (!empty($dir[0]) && $dir[0] == '.') {
9316
                $dir = substr($dir, 1);
9317
            }
9318
            if (!empty($dir[0]) && $dir[0] != '/') {
9319
                $dir = '/'.$dir;
9320
            }
9321
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
9322
                $dir .= '/';
9323
            }
9324
        } else {
9325
            $parentInfo = DocumentManager::get_document_data_by_id(
9326
                $parentId,
9327
                $courseInfo['code']
9328
            );
9329
            if (!empty($parentInfo)) {
9330
                $dir = $parentInfo['path'].'/';
9331
            }
9332
        }
9333
9334
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9335
9336
        if (!is_dir($filepath)) {
9337
            $dir = '/';
9338
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9339
        }
9340
9341
        $originalTitle = !empty($title) ? $title : $_POST['title'];
9342
9343
        if (!empty($title)) {
9344
            $title = api_replace_dangerous_char(stripslashes($title));
9345
        } else {
9346
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
9347
        }
9348
9349
        $title = disable_dangerous_file($title);
9350
        $filename = $title;
9351
        $content = !empty($content) ? $content : $_POST['content_lp'];
9352
        $tmpFileName = $filename;
9353
9354
        $i = 0;
9355
        while (file_exists($filepath.$tmpFileName.'.html')) {
9356
            $tmpFileName = $filename.'_'.++$i;
9357
        }
9358
9359
        $filename = $tmpFileName.'.html';
9360
        $content = stripslashes($content);
9361
9362
        if (file_exists($filepath.$filename)) {
9363
            return 0;
9364
        }
9365
9366
        $putContent = file_put_contents($filepath.$filename, $content);
9367
9368
        if ($putContent === false) {
9369
            return 0;
9370
        }
9371
9372
        $fileSize = filesize($filepath.$filename);
9373
        $saveFilePath = $dir.$filename;
9374
9375
        $documentId = add_document(
9376
            $courseInfo,
9377
            $saveFilePath,
9378
            'file',
9379
            $fileSize,
9380
            $tmpFileName,
9381
            '',
9382
            0, //readonly
9383
            true,
9384
            null,
9385
            $sessionId,
9386
            $creatorId
9387
        );
9388
9389
        if (!$documentId) {
9390
            return 0;
9391
        }
9392
9393
        api_item_property_update(
9394
            $courseInfo,
9395
            TOOL_DOCUMENT,
9396
            $documentId,
9397
            'DocumentAdded',
9398
            $creatorId,
9399
            null,
9400
            null,
9401
            null,
9402
            null,
9403
            $sessionId
9404
        );
9405
9406
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
9407
        $newTitle = $originalTitle;
9408
9409
        if ($newComment || $newTitle) {
9410
            $em = Database::getManager();
9411
9412
            /** @var CDocument $doc */
9413
            $doc = $em->find('ChamiloCourseBundle:CDocument', $documentId);
9414
9415
            if ($newComment) {
9416
                $doc->setComment($newComment);
9417
            }
9418
9419
            if ($newTitle) {
9420
                $doc->setTitle($newTitle);
9421
            }
9422
9423
            $em->persist($doc);
9424
            $em->flush();
9425
        }
9426
9427
        return $documentId;
9428
    }
9429
9430
    /**
9431
     * Return HTML form to add/edit a link item.
9432
     *
9433
     * @param string $action     (add/edit)
9434
     * @param int    $id         Item ID if exists
9435
     * @param mixed  $extra_info
9436
     *
9437
     * @throws Exception
9438
     * @throws HTML_QuickForm_Error
9439
     *
9440
     * @return string HTML form
9441
     */
9442
    public function display_link_form(
9443
        $action = 'add',
9444
        $id = 0,
9445
        $extra_info = '',
9446
        $item = null,
9447
        $excludeExtraFields = []
9448
    ) {
9449
        $course_id = api_get_course_int_id();
9450
        $tbl_link = Database::get_course_table(TABLE_LINK);
9451
9452
        $item_title = '';
9453
        $item_description = '';
9454
        $item_url = '';
9455
9456
        $previousId = 0;
9457
        if ($id != 0 && is_array($extra_info)) {
9458
            $item_title = stripslashes($extra_info['title']);
9459
            $item_description = stripslashes($extra_info['description']);
9460
            $item_url = stripslashes($extra_info['url']);
9461
            $previousId = $extra_info['previous_item_id'];
9462
        } elseif (is_numeric($extra_info)) {
9463
            $extra_info = (int) $extra_info;
9464
            $sql = "SELECT title, description, url
9465
                    FROM $tbl_link
9466
                    WHERE c_id = $course_id AND iid = $extra_info";
9467
            $result = Database::query($sql);
9468
            $row = Database::fetch_array($result);
9469
            $item_title = $row['title'];
9470
            $item_description = $row['description'];
9471
            $item_url = $row['url'];
9472
        }
9473
9474
        if ($item instanceof learnpathItem) {
9475
            $previousId = $extra_info->previous;
9476
        }
9477
9478
        $form = new FormValidator(
9479
            'edit_link',
9480
            'POST',
9481
            $this->getCurrentBuildingModeURL()
9482
        );
9483
        $defaults = [];
9484
        $parent = 0;
9485
        if ($id != 0 && is_array($extra_info)) {
9486
            $parent = $extra_info['parent_item_id'];
9487
        }
9488
9489
        $arrLP = $this->getItemsForForm();
9490
9491
        $this->tree_array($arrLP);
9492
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9493
        unset($this->arrMenu);
9494
9495
        if ($action === 'add') {
9496
            $legend = get_lang('CreateTheLink');
9497
        } elseif ($action === 'move') {
9498
            $legend = get_lang('MoveCurrentLink');
9499
        } else {
9500
            $legend = get_lang('EditCurrentLink');
9501
        }
9502
9503
        $form->addHeader($legend);
9504
9505
        if ($action !== 'move') {
9506
            $this->setItemTitle($form);
9507
            $defaults['title'] = $item_title;
9508
        }
9509
9510
        $selectParent = $form->addSelect(
9511
            'parent',
9512
            get_lang('Parent'),
9513
            [],
9514
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
9515
        );
9516
        $selectParent->addOption($this->name, 0);
9517
        $arrHide = [
9518
            $id,
9519
        ];
9520
9521
        $parent_item_id = Session::read('parent_item_id', 0);
9522
9523
        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...
9524
            if ($action != 'add') {
9525
                if (
9526
                    ($arrLP[$i]['item_type'] == 'dir') &&
9527
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9528
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9529
                ) {
9530
                    $selectParent->addOption(
9531
                        $arrLP[$i]['title'],
9532
                        $arrLP[$i]['id'],
9533
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
9534
                    );
9535
9536
                    if ($parent == $arrLP[$i]['id']) {
9537
                        $selectParent->setSelected($arrLP[$i]['id']);
9538
                    }
9539
                } else {
9540
                    $arrHide[] = $arrLP[$i]['id'];
9541
                }
9542
            } else {
9543
                if ($arrLP[$i]['item_type'] == 'dir') {
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_item_id == $arrLP[$i]['id']) {
9551
                        $selectParent->setSelected($arrLP[$i]['id']);
9552
                    }
9553
                }
9554
            }
9555
        }
9556
9557
        if (is_array($arrLP)) {
9558
            reset($arrLP);
9559
        }
9560
9561
        $selectPrevious = $form->addSelect(
9562
            'previous',
9563
            get_lang('Position'),
9564
            [],
9565
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9566
        );
9567
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
9568
9569
        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...
9570
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9571
                $selectPrevious->addOption(
9572
                    $arrLP[$i]['title'],
9573
                    $arrLP[$i]['id']
9574
                );
9575
9576
                if ($previousId == $arrLP[$i]['id']) {
9577
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9578
                } elseif ($action === 'add') {
9579
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9580
                }
9581
            }
9582
        }
9583
9584
        if ($action !== 'move') {
9585
            $urlAttributes = ['class' => 'learnpath_item_form'];
9586
9587
            if (is_numeric($extra_info)) {
9588
                $urlAttributes['disabled'] = 'disabled';
9589
            }
9590
9591
            $form->addElement('url', 'url', get_lang('Url'), $urlAttributes);
9592
            $defaults['url'] = $item_url;
9593
            $arrHide = [];
9594
            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...
9595
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
9596
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9597
                }
9598
            }
9599
        }
9600
9601
        if ('edit' === $action) {
9602
            $extraField = new ExtraField('lp_item');
9603
            $extraField->addElements($form, $id, $excludeExtraFields);
9604
        }
9605
9606
        if ($action === 'add') {
9607
            $form->addButtonSave(get_lang('AddLinkToCourse'), 'submit_button');
9608
        } else {
9609
            $form->addButtonSave(get_lang('EditCurrentLink'), 'submit_button');
9610
        }
9611
9612
        if ($action === 'move') {
9613
            $form->addHidden('title', $item_title);
9614
            $form->addHidden('description', $item_description);
9615
        }
9616
9617
        if (is_numeric($extra_info)) {
9618
            $form->addHidden('path', $extra_info);
9619
        } elseif (is_array($extra_info)) {
9620
            $form->addHidden('path', $extra_info['path']);
9621
        }
9622
        $form->addHidden('type', TOOL_LINK);
9623
        $form->addHidden('post_time', time());
9624
        $form->setDefaults($defaults);
9625
9626
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
9627
    }
9628
9629
    /**
9630
     * Return HTML form to add/edit a student publication (work).
9631
     *
9632
     * @param string $action
9633
     * @param int    $id         Item ID if already exists
9634
     * @param string $extra_info
9635
     *
9636
     * @throws Exception
9637
     *
9638
     * @return string HTML form
9639
     */
9640
    public function display_student_publication_form(
9641
        $action = 'add',
9642
        $id = 0,
9643
        $extra_info = '',
9644
        $item = null,
9645
        $excludeExtraFields = []
9646
    ) {
9647
        $course_id = api_get_course_int_id();
9648
        $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
9649
9650
        $item_title = get_lang('Student_publication');
9651
        $previousId = 0;
9652
        if ($id != 0 && is_array($extra_info)) {
9653
            $item_title = stripslashes($extra_info['title']);
9654
            $item_description = stripslashes($extra_info['description']);
9655
            $previousId = $extra_info['previous_item_id'];
9656
        } elseif (is_numeric($extra_info)) {
9657
            $extra_info = (int) $extra_info;
9658
            $sql = "SELECT title, description
9659
                    FROM $tbl_publication
9660
                    WHERE c_id = $course_id AND id = ".$extra_info;
9661
9662
            $result = Database::query($sql);
9663
            $row = Database::fetch_array($result);
9664
            if ($row) {
9665
                $item_title = $row['title'];
9666
            }
9667
        }
9668
9669
        if ($item instanceof learnpathItem) {
9670
            $item_title = $item->get_title();
9671
            $item_description = $item->get_description();
9672
            $previousId = $item->previous;
9673
        }
9674
9675
        $parent = 0;
9676
        if ($id != 0 && is_array($extra_info)) {
9677
            $parent = $extra_info['parent_item_id'];
9678
        }
9679
9680
        $arrLP = $this->getItemsForForm();
9681
9682
        $this->tree_array($arrLP);
9683
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9684
        unset($this->arrMenu);
9685
9686
        $form = new FormValidator('frm_student_publication', 'post', '#');
9687
9688
        if ($action === 'add') {
9689
            $form->addHeader(get_lang('Student_publication'));
9690
        } elseif ($action === 'move') {
9691
            $form->addHeader(get_lang('MoveCurrentStudentPublication'));
9692
        } else {
9693
            $form->addHeader(get_lang('EditCurrentStudentPublication'));
9694
        }
9695
9696
        if ($action !== 'move') {
9697
            $this->setItemTitle($form);
9698
        }
9699
9700
        $parentSelect = $form->addSelect(
9701
            'parent',
9702
            get_lang('Parent'),
9703
            ['0' => $this->name],
9704
            [
9705
                'onchange' => 'javascript: load_cbo(this.value);',
9706
                'class' => 'learnpath_item_form',
9707
                'id' => 'idParent',
9708
            ]
9709
        );
9710
9711
        $arrHide = [$id];
9712
        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...
9713
            if ($action != 'add') {
9714
                if (
9715
                    ($arrLP[$i]['item_type'] == 'dir') &&
9716
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9717
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9718
                ) {
9719
                    $parentSelect->addOption(
9720
                        $arrLP[$i]['title'],
9721
                        $arrLP[$i]['id'],
9722
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9723
                    );
9724
9725
                    if ($parent == $arrLP[$i]['id']) {
9726
                        $parentSelect->setSelected($arrLP[$i]['id']);
9727
                    }
9728
                } else {
9729
                    $arrHide[] = $arrLP[$i]['id'];
9730
                }
9731
            } else {
9732
                if ($arrLP[$i]['item_type'] === 'dir') {
9733
                    $parentSelect->addOption(
9734
                        $arrLP[$i]['title'],
9735
                        $arrLP[$i]['id'],
9736
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9737
                    );
9738
9739
                    if ($parent == $arrLP[$i]['id']) {
9740
                        $parentSelect->setSelected($arrLP[$i]['id']);
9741
                    }
9742
                }
9743
            }
9744
        }
9745
9746
        if (is_array($arrLP)) {
9747
            reset($arrLP);
9748
        }
9749
9750
        $previousSelect = $form->addSelect(
9751
            'previous',
9752
            get_lang('Position'),
9753
            ['0' => get_lang('FirstPosition')],
9754
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9755
        );
9756
9757
        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...
9758
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9759
                $previousSelect->addOption(
9760
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
9761
                    $arrLP[$i]['id']
9762
                );
9763
9764
                if ($previousId == $arrLP[$i]['id']) {
9765
                    $previousSelect->setSelected($arrLP[$i]['id']);
9766
                } elseif ($action === 'add') {
9767
                    $previousSelect->setSelected($arrLP[$i]['id']);
9768
                }
9769
            }
9770
        }
9771
9772
        if ('edit' === $action) {
9773
            $extraField = new ExtraField('lp_item');
9774
            $extraField->addElements($form, $id, $excludeExtraFields);
9775
        }
9776
9777
        if ($action === 'add') {
9778
            $form->addButtonCreate(get_lang('AddAssignmentToCourse'), 'submit_button');
9779
        } else {
9780
            $form->addButtonCreate(get_lang('EditCurrentStudentPublication'), 'submit_button');
9781
        }
9782
9783
        if ($action === 'move') {
9784
            $form->addHidden('title', $item_title);
9785
            $form->addHidden('description', $item_description);
9786
        }
9787
9788
        if (is_numeric($extra_info)) {
9789
            $form->addHidden('path', $extra_info);
9790
        } elseif (is_array($extra_info)) {
9791
            $form->addHidden('path', $extra_info['path']);
9792
        }
9793
9794
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
9795
        $form->addHidden('post_time', time());
9796
        $form->setDefaults(['title' => $item_title]);
9797
9798
        $return = '<div class="sectioncomment">';
9799
        $return .= $form->returnForm();
9800
        $return .= '</div>';
9801
9802
        return $return;
9803
    }
9804
9805
    /**
9806
     * Displays the menu for manipulating a step.
9807
     *
9808
     * @param int    $item_id
9809
     * @param string $item_type
9810
     *
9811
     * @return string
9812
     */
9813
    public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
9814
    {
9815
        $_course = api_get_course_info();
9816
        $course_code = api_get_course_id();
9817
        $item_id = (int) $item_id;
9818
9819
        $return = '<div class="actions">';
9820
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9821
        $sql = "SELECT * FROM $tbl_lp_item
9822
                WHERE iid = ".$item_id;
9823
        $result = Database::query($sql);
9824
        $row = Database::fetch_assoc($result);
9825
9826
        $audio_player = null;
9827
        // We display an audio player if needed.
9828
        if (!empty($row['audio'])) {
9829
            $audio = learnpathItem::fixAudio($row['audio']);
9830
            $webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document'.$audio;
9831
            $audio_player .= '<div class="lp_mediaplayer" id="container">
9832
                            <audio src="'.$webAudioPath.'" controls>
9833
                            </div><br />';
9834
        }
9835
9836
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
9837
9838
        if ($item_type != TOOL_LP_FINAL_ITEM) {
9839
            $return .= Display::url(
9840
                Display::return_icon(
9841
                    'edit.png',
9842
                    get_lang('Edit'),
9843
                    [],
9844
                    ICON_SIZE_SMALL
9845
                ),
9846
                $url.'&action=edit_item&path_item='.$row['path']
9847
            );
9848
9849
            $return .= Display::url(
9850
                Display::return_icon(
9851
                    'move.png',
9852
                    get_lang('Move'),
9853
                    [],
9854
                    ICON_SIZE_SMALL
9855
                ),
9856
                $url.'&action=move_item'
9857
            );
9858
        }
9859
9860
        // Commented for now as prerequisites cannot be added to chapters.
9861
        if ($item_type != 'dir') {
9862
            $return .= Display::url(
9863
                Display::return_icon(
9864
                    'accept.png',
9865
                    get_lang('LearnpathPrerequisites'),
9866
                    [],
9867
                    ICON_SIZE_SMALL
9868
                ),
9869
                $url.'&action=edit_item_prereq'
9870
            );
9871
        }
9872
        $return .= Display::url(
9873
            Display::return_icon(
9874
                'delete.png',
9875
                get_lang('Delete'),
9876
                [],
9877
                ICON_SIZE_SMALL
9878
            ),
9879
            $url.'&action=delete_item'
9880
        );
9881
9882
        if (in_array($item_type, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
9883
            $documentData = DocumentManager::get_document_data_by_id($row['path'], $course_code);
9884
            if (empty($documentData)) {
9885
                // Try with iid
9886
                $table = Database::get_course_table(TABLE_DOCUMENT);
9887
                $sql = "SELECT path FROM $table
9888
                        WHERE
9889
                              c_id = ".api_get_course_int_id()." AND
9890
                              iid = ".$row['path']." AND
9891
                              path NOT LIKE '%_DELETED_%'";
9892
                $result = Database::query($sql);
9893
                $documentData = Database::fetch_array($result);
9894
                if ($documentData) {
9895
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
9896
                }
9897
            }
9898
            if (isset($documentData['absolute_path_from_document'])) {
9899
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
9900
            }
9901
        }
9902
9903
        $return .= '</div>';
9904
9905
        if (!empty($audio_player)) {
9906
            $return .= $audio_player;
9907
        }
9908
9909
        return $return;
9910
    }
9911
9912
    /**
9913
     * Creates the javascript needed for filling up the checkboxes without page reload.
9914
     *
9915
     * @return string
9916
     */
9917
    public function get_js_dropdown_array()
9918
    {
9919
        $course_id = api_get_course_int_id();
9920
        $return = 'var child_name = new Array();'."\n";
9921
        $return .= 'var child_value = new Array();'."\n\n";
9922
        $return .= 'child_name[0] = new Array();'."\n";
9923
        $return .= 'child_value[0] = new Array();'."\n\n";
9924
9925
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9926
        $i = 0;
9927
        $list = $this->getItemsForForm(true);
9928
9929
        foreach ($list as $row_zero) {
9930
            if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
9931
                if ($row_zero['item_type'] == TOOL_QUIZ) {
9932
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
9933
                }
9934
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
9935
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
9936
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
9937
            }
9938
        }
9939
9940
        $return .= "\n";
9941
        $sql = "SELECT * FROM $tbl_lp_item
9942
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9943
        $res = Database::query($sql);
9944
        while ($row = Database::fetch_array($res)) {
9945
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
9946
                           WHERE
9947
                                c_id = ".$course_id." AND
9948
                                parent_item_id = ".$row['iid']."
9949
                           ORDER BY display_order ASC";
9950
            $res_parent = Database::query($sql_parent);
9951
            $i = 0;
9952
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
9953
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
9954
9955
            while ($row_parent = Database::fetch_array($res_parent)) {
9956
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
9957
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
9958
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
9959
            }
9960
            $return .= "\n";
9961
        }
9962
9963
        $return .= "
9964
            function load_cbo(id) {
9965
                if (!id) {
9966
                    return false;
9967
                }
9968
9969
                var cbo = document.getElementById('previous');
9970
                for (var i = cbo.length - 1; i > 0; i--) {
9971
                    cbo.options[i] = null;
9972
                }
9973
9974
                var k=0;
9975
                for(var i = 1; i <= child_name[id].length; i++){
9976
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
9977
                    option.style.paddingLeft = '40px';
9978
                    cbo.options[i] = option;
9979
                    k = i;
9980
                }
9981
9982
                cbo.options[k].selected = true;
9983
                $('#previous').selectpicker('refresh');
9984
            }";
9985
9986
        return $return;
9987
    }
9988
9989
    /**
9990
     * Display the form to allow moving an item.
9991
     *
9992
     * @param learnpathItem $item Item ID
9993
     *
9994
     * @throws Exception
9995
     * @throws HTML_QuickForm_Error
9996
     *
9997
     * @return string HTML form
9998
     */
9999
    public function display_move_item($item)
10000
    {
10001
        $return = '';
10002
        if ($item) {
10003
            $item_id = $item->getIid();
10004
            $type = $item->get_type();
10005
            $path = (int) $item->get_path();
10006
10007
            switch ($type) {
10008
                case 'dir':
10009
                case 'asset':
10010
                    $return .= $this->display_manipulate($item_id, $type);
10011
                    $return .= $this->display_item_form(
10012
                        $type,
10013
                        get_lang('MoveCurrentChapter'),
10014
                        'move',
10015
                        $item_id,
10016
                        $item
10017
                    );
10018
                    break;
10019
                case TOOL_DOCUMENT:
10020
                    $return .= $this->display_manipulate($item_id, $type);
10021
                    $return .= $this->display_document_form('move', $item_id, null, $item);
10022
                    break;
10023
                case TOOL_LINK:
10024
                    $return .= $this->display_manipulate($item_id, $type);
10025
                    $return .= $this->display_link_form('move', $item_id, $path, $item);
10026
                    break;
10027
                case TOOL_HOTPOTATOES:
10028
                    $return .= $this->display_manipulate($item_id, $type);
10029
                    $return .= $this->display_link_form('move', $item_id, $item);
10030
                    break;
10031
                case TOOL_QUIZ:
10032
                    $return .= $this->display_manipulate($item_id, $type);
10033
                    $return .= $this->display_quiz_form('move', $item_id, $item);
10034
                    break;
10035
                case TOOL_STUDENTPUBLICATION:
10036
                    $return .= $this->display_manipulate($item_id, $type);
10037
                    $return .= $this->display_student_publication_form('move', $item_id, $path, $item);
10038
                    break;
10039
                case TOOL_FORUM:
10040
                    $return .= $this->display_manipulate($item_id, $type);
10041
                    $return .= $this->display_forum_form('move', $item_id, $path);
10042
                    break;
10043
                case TOOL_THREAD:
10044
                    $return .= $this->display_manipulate($item_id, $type);
10045
                    $return .= $this->display_forum_form('move', $item_id, $path);
10046
                    break;
10047
            }
10048
        }
10049
10050
        return $return;
10051
    }
10052
10053
    /**
10054
     * Return HTML form to allow prerequisites selection.
10055
     *
10056
     * @todo use FormValidator
10057
     *
10058
     * @param int Item ID
10059
     *
10060
     * @return string HTML form
10061
     */
10062
    public function display_item_prerequisites_form($item_id = 0)
10063
    {
10064
        $course_id = api_get_course_int_id();
10065
        $item_id = (int) $item_id;
10066
10067
        if (empty($item_id)) {
10068
            return '';
10069
        }
10070
10071
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10072
10073
        /* Current prerequisite */
10074
        $sql = "SELECT * FROM $tbl_lp_item
10075
                WHERE iid = $item_id";
10076
        $result = Database::query($sql);
10077
        $row = Database::fetch_array($result);
10078
        $prerequisiteId = $row['prerequisite'];
10079
10080
        $return = '<legend>';
10081
        $return .= get_lang('AddEditPrerequisites');
10082
        $return .= '</legend>';
10083
        $return .= '<form method="POST">';
10084
        $return .= '<div class="table-responsive">';
10085
        $return .= '<table class="table table-hover">';
10086
        $return .= '<thead>';
10087
        $return .= '<tr>';
10088
        $return .= '<th>'.get_lang('LearnpathPrerequisites').'</th>';
10089
        $return .= '<th width="140">'.get_lang('Minimum').'</th>';
10090
        $return .= '<th width="140">'.get_lang('Maximum').'</th>';
10091
        $return .= '</tr>';
10092
        $return .= '</thead>';
10093
10094
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
10095
        $return .= '<tbody>';
10096
        $return .= '<tr>';
10097
        $return .= '<td colspan="3">';
10098
        $return .= '<div class="radio learnpath"><label for="idNone">';
10099
        $return .= '<input checked="checked" id="idNone" name="prerequisites" type="radio" />';
10100
        $return .= get_lang('None').'</label>';
10101
        $return .= '</div>';
10102
        $return .= '</tr>';
10103
10104
        $sql = "SELECT * FROM $tbl_lp_item
10105
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
10106
        $result = Database::query($sql);
10107
10108
        $selectedMinScore = [];
10109
        $selectedMaxScore = [];
10110
        $masteryScore = [];
10111
        while ($row = Database::fetch_array($result)) {
10112
            if ($row['iid'] == $item_id) {
10113
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
10114
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
10115
            }
10116
            $masteryScore[$row['iid']] = $row['mastery_score'];
10117
        }
10118
10119
        $arrLP = $this->getItemsForForm();
10120
        $this->tree_array($arrLP);
10121
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
10122
        unset($this->arrMenu);
10123
10124
        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...
10125
            $item = $arrLP[$i];
10126
10127
            if ($item['id'] == $item_id) {
10128
                break;
10129
            }
10130
10131
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
10132
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
10133
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
10134
10135
            $return .= '<tr>';
10136
            $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
10137
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
10138
            $return .= '<label for="id'.$item['id'].'">';
10139
10140
            $checked = '';
10141
            if (null !== $prerequisiteId) {
10142
                $checked = in_array($prerequisiteId, [$item['id'], $item['ref']]) ? ' checked="checked" ' : '';
10143
            }
10144
10145
            $disabled = $item['item_type'] === 'dir' ? ' disabled="disabled" ' : '';
10146
10147
            $return .= '<input '.$checked.' '.$disabled.' id="id'.$item['id'].'" name="prerequisites" type="radio" value="'.$item['id'].'" />';
10148
10149
            $icon_name = str_replace(' ', '', $item['item_type']);
10150
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
10151
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
10152
            } else {
10153
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
10154
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
10155
                } else {
10156
                    $return .= Display::return_icon('folder_document.png');
10157
                }
10158
            }
10159
10160
            $return .= $item['title'].'</label>';
10161
            $return .= '</div>';
10162
            $return .= '</td>';
10163
10164
            if ($item['item_type'] == TOOL_QUIZ) {
10165
                // lets update max_score Quiz information depending of the Quiz Advanced properties
10166
                $lpItemObj = new LpItem($course_id, $item['id']);
10167
                $exercise = new Exercise($course_id);
10168
                $exercise->read($lpItemObj->path);
10169
                $lpItemObj->max_score = $exercise->get_max_score();
10170
                $lpItemObj->update();
10171
                $item['max_score'] = $lpItemObj->max_score;
10172
10173
                //if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
10174
                if (!isset($selectedMinScore[$item['id']]) && !empty($masteryScoreAsMinValue)) {
10175
                    // Backwards compatibility with 1.9.x use mastery_score as min value
10176
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
10177
                }
10178
10179
                $return .= '<td>';
10180
                $return .= '<input
10181
                    class="form-control"
10182
                    size="4" maxlength="3"
10183
                    name="min_'.$item['id'].'"
10184
                    type="number"
10185
                    min="0"
10186
                    step="any"
10187
                    max="'.$item['max_score'].'"
10188
                    value="'.$selectedMinScoreValue.'"
10189
                />';
10190
                $return .= '</td>';
10191
                $return .= '<td>';
10192
                $return .= '<input
10193
                    class="form-control"
10194
                    size="4"
10195
                    maxlength="3"
10196
                    name="max_'.$item['id'].'"
10197
                    type="number"
10198
                    min="0"
10199
                    step="any"
10200
                    max="'.$item['max_score'].'"
10201
                    value="'.$selectedMaxScoreValue.'"
10202
                />';
10203
                $return .= '</td>';
10204
            }
10205
10206
            if ($item['item_type'] == TOOL_HOTPOTATOES) {
10207
                $return .= '<td>';
10208
                $return .= '<input
10209
                    size="4"
10210
                    maxlength="3"
10211
                    name="min_'.$item['id'].'"
10212
                    type="number"
10213
                    min="0"
10214
                    step="any"
10215
                    max="'.$item['max_score'].'"
10216
                    value="'.$selectedMinScoreValue.'"
10217
                />';
10218
                $return .= '</td>';
10219
                $return .= '<td>';
10220
                $return .= '<input
10221
                    size="4"
10222
                    maxlength="3"
10223
                    name="max_'.$item['id'].'"
10224
                    type="number"
10225
                    min="0"
10226
                    step="any"
10227
                    max="'.$item['max_score'].'"
10228
                    value="'.$selectedMaxScoreValue.'"
10229
                />';
10230
                $return .= '</td>';
10231
            }
10232
            $return .= '</tr>';
10233
        }
10234
        $return .= '<tr>';
10235
        $return .= '</tr>';
10236
        $return .= '</tbody>';
10237
        $return .= '</table>';
10238
        $return .= '</div>';
10239
        $return .= '<div class="form-group">';
10240
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
10241
            get_lang('ModifyPrerequisites').'</button>';
10242
        $return .= '</form>';
10243
10244
        return $return;
10245
    }
10246
10247
    /**
10248
     * Return HTML list to allow prerequisites selection for lp.
10249
     *
10250
     * @return string HTML form
10251
     */
10252
    public function display_lp_prerequisites_list()
10253
    {
10254
        $course_id = api_get_course_int_id();
10255
        $lp_id = $this->lp_id;
10256
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
10257
10258
        // get current prerequisite
10259
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
10260
        $result = Database::query($sql);
10261
        $row = Database::fetch_array($result);
10262
        $prerequisiteId = $row['prerequisite'];
10263
        $session_id = api_get_session_id();
10264
        $session_condition = api_get_session_condition($session_id, true, true);
10265
        $sql = "SELECT * FROM $tbl_lp
10266
                WHERE c_id = $course_id $session_condition
10267
                ORDER BY display_order ";
10268
        $rs = Database::query($sql);
10269
        $return = '';
10270
        $return .= '<select name="prerequisites" class="form-control">';
10271
        $return .= '<option value="0">'.get_lang('None').'</option>';
10272
        if (Database::num_rows($rs) > 0) {
10273
            while ($row = Database::fetch_array($rs)) {
10274
                if ($row['id'] == $lp_id) {
10275
                    continue;
10276
                }
10277
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
10278
            }
10279
        }
10280
        $return .= '</select>';
10281
10282
        return $return;
10283
    }
10284
10285
    /**
10286
     * Creates a list with all the documents in it.
10287
     *
10288
     * @param bool $showInvisibleFiles
10289
     *
10290
     * @throws Exception
10291
     * @throws HTML_QuickForm_Error
10292
     *
10293
     * @return string
10294
     */
10295
    public function get_documents($showInvisibleFiles = false)
10296
    {
10297
        $course_info = api_get_course_info();
10298
        $sessionId = api_get_session_id();
10299
        $documentTree = DocumentManager::get_document_preview(
10300
            $course_info,
10301
            $this->lp_id,
10302
            null,
10303
            $sessionId,
10304
            true,
10305
            null,
10306
            null,
10307
            $showInvisibleFiles,
10308
            true
10309
        );
10310
10311
        $headers = [
10312
            get_lang('Files'),
10313
            get_lang('CreateTheDocument'),
10314
            get_lang('CreateReadOutText'),
10315
            get_lang('Upload'),
10316
        ];
10317
10318
        $form = new FormValidator(
10319
            'form_upload',
10320
            'POST',
10321
            $this->getCurrentBuildingModeURL(),
10322
            '',
10323
            ['enctype' => 'multipart/form-data']
10324
        );
10325
10326
        $folders = DocumentManager::get_all_document_folders(
10327
            api_get_course_info(),
10328
            0,
10329
            true
10330
        );
10331
10332
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
10333
10334
        DocumentManager::build_directory_selector(
10335
            $folders,
10336
            $lpPathInfo['id'],
10337
            [],
10338
            true,
10339
            $form,
10340
            'directory_parent_id'
10341
        );
10342
10343
        $group = [
10344
            $form->createElement(
10345
                'radio',
10346
                'if_exists',
10347
                get_lang('UplWhatIfFileExists'),
10348
                get_lang('UplDoNothing'),
10349
                'nothing'
10350
            ),
10351
            $form->createElement(
10352
                'radio',
10353
                'if_exists',
10354
                null,
10355
                get_lang('UplOverwriteLong'),
10356
                'overwrite'
10357
            ),
10358
            $form->createElement(
10359
                'radio',
10360
                'if_exists',
10361
                null,
10362
                get_lang('UplRenameLong'),
10363
                'rename'
10364
            ),
10365
        ];
10366
        $form->addGroup($group, null, get_lang('UplWhatIfFileExists'));
10367
10368
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
10369
        $defaultFileExistsOption = 'rename';
10370
        if (!empty($fileExistsOption)) {
10371
            $defaultFileExistsOption = $fileExistsOption;
10372
        }
10373
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
10374
10375
        // Check box options
10376
        $form->addElement(
10377
            'checkbox',
10378
            'unzip',
10379
            get_lang('Options'),
10380
            get_lang('Uncompress')
10381
        );
10382
10383
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
10384
        $form->addMultipleUpload($url);
10385
        $new = $this->display_document_form('add', 0);
10386
        $frmReadOutText = $this->displayFrmReadOutText('add');
10387
        $tabs = Display::tabs(
10388
            $headers,
10389
            [$documentTree, $new, $frmReadOutText, $form->returnForm()],
10390
            'subtab'
10391
        );
10392
10393
        return $tabs;
10394
    }
10395
10396
    /**
10397
     * Creates a list with all the exercises (quiz) in it.
10398
     *
10399
     * @return string
10400
     */
10401
    public function get_exercises()
10402
    {
10403
        $course_id = api_get_course_int_id();
10404
        $session_id = api_get_session_id();
10405
        $userInfo = api_get_user_info();
10406
10407
        // New for hotpotatoes.
10408
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
10409
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
10410
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
10411
        $condition_session = api_get_session_condition($session_id, true, true);
10412
        $setting = api_get_configuration_value('show_invisible_exercise_in_lp_list');
10413
10414
        $activeCondition = ' active <> -1 ';
10415
        if ($setting) {
10416
            $activeCondition = ' active = 1 ';
10417
        }
10418
10419
        $categoryCondition = '';
10420
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
10421
        if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
10422
            $categoryCondition = " AND exercise_category_id = $categoryId ";
10423
        }
10424
10425
        $keywordCondition = '';
10426
        $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
10427
10428
        if (!empty($keyword)) {
10429
            $keyword = Database::escape_string($keyword);
10430
            $keywordCondition = " AND title LIKE '%$keyword%' ";
10431
        }
10432
10433
        $sql_quiz = "SELECT * FROM $tbl_quiz
10434
                     WHERE
10435
                            c_id = $course_id AND
10436
                            $activeCondition
10437
                            $condition_session
10438
                            $categoryCondition
10439
                            $keywordCondition
10440
                     ORDER BY title ASC";
10441
10442
        $sql_hot = "SELECT * FROM $tbl_doc
10443
                    WHERE
10444
                        c_id = $course_id AND
10445
                        path LIKE '".$uploadPath."/%/%htm%'
10446
                        $condition_session
10447
                     ORDER BY id ASC";
10448
10449
        $res_quiz = Database::query($sql_quiz);
10450
        $res_hot = Database::query($sql_hot);
10451
10452
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
10453
10454
        // Create a search-box
10455
        $form = new FormValidator('search_simple', 'get', $currentUrl);
10456
        $form->addHidden('action', 'add_item');
10457
        $form->addHidden('type', 'step');
10458
        $form->addHidden('lp_id', $this->lp_id);
10459
        $form->addHidden('lp_build_selected', '2');
10460
10461
        $form->addCourseHiddenParams();
10462
        $form->addText(
10463
            'keyword',
10464
            get_lang('Search'),
10465
            false,
10466
            [
10467
                'aria-label' => get_lang('Search'),
10468
            ]
10469
        );
10470
10471
        if (api_get_configuration_value('allow_exercise_categories')) {
10472
            $manager = new ExerciseCategoryManager();
10473
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
10474
            if (!empty($options)) {
10475
                $form->addSelect(
10476
                    'category_id',
10477
                    get_lang('Category'),
10478
                    $options,
10479
                    ['placeholder' => get_lang('SelectAnOption')]
10480
                );
10481
            }
10482
        }
10483
10484
        $form->addButtonSearch(get_lang('Search'));
10485
        $return = $form->returnForm();
10486
10487
        $return .= '<ul class="lp_resource">';
10488
10489
        $return .= '<li class="lp_resource_element">';
10490
        $return .= Display::return_icon('new_exercice.png');
10491
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
10492
            get_lang('NewExercise').'</a>';
10493
        $return .= '</li>';
10494
10495
        $previewIcon = Display::return_icon(
10496
            'preview_view.png',
10497
            get_lang('Preview')
10498
        );
10499
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
10500
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10501
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/showinframes.php?'.api_get_cidreq();
10502
10503
        // Display hotpotatoes
10504
        while ($row_hot = Database::fetch_array($res_hot)) {
10505
            $link = Display::url(
10506
                $previewIcon,
10507
                $exerciseUrl.'&file='.$row_hot['path'],
10508
                ['target' => '_blank']
10509
            );
10510
            $return .= '<li class="lp_resource_element" data_id="'.$row_hot['id'].'" data_type="hotpotatoes" title="'.$row_hot['title'].'" >';
10511
            $return .= '<a class="moved" href="#">';
10512
            $return .= Display::return_icon(
10513
                'move_everywhere.png',
10514
                get_lang('Move'),
10515
                [],
10516
                ICON_SIZE_TINY
10517
            );
10518
            $return .= '</a> ';
10519
            $return .= Display::return_icon('hotpotatoes_s.png');
10520
            $return .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_HOTPOTATOES.'&file='.$row_hot['id'].'&lp_id='.$this->lp_id.'">'.
10521
                ((!empty($row_hot['comment'])) ? $row_hot['comment'] : Security::remove_XSS($row_hot['title'])).$link.'</a>';
10522
            $return .= '</li>';
10523
        }
10524
10525
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
10526
        while ($row_quiz = Database::fetch_array($res_quiz)) {
10527
            $title = strip_tags(
10528
                api_html_entity_decode($row_quiz['title'])
10529
            );
10530
10531
            $visibility = api_get_item_visibility(
10532
                ['real_id' => $course_id],
10533
                TOOL_QUIZ,
10534
                $row_quiz['iid'],
10535
                $session_id
10536
            );
10537
10538
            $link = Display::url(
10539
                $previewIcon,
10540
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
10541
                ['target' => '_blank']
10542
            );
10543
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
10544
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
10545
            $return .= $quizIcon;
10546
            $sessionStar = api_get_session_image(
10547
                $row_quiz['session_id'],
10548
                $userInfo['status']
10549
            );
10550
            $return .= Display::url(
10551
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
10552
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['id'].'&lp_id='.$this->lp_id,
10553
                [
10554
                    'class' => $visibility == 0 ? 'moved text-muted' : 'moved',
10555
                ]
10556
            );
10557
            $return .= '</li>';
10558
        }
10559
10560
        $return .= '</ul>';
10561
10562
        return $return;
10563
    }
10564
10565
    /**
10566
     * Creates a list with all the links in it.
10567
     *
10568
     * @return string
10569
     */
10570
    public function get_links()
10571
    {
10572
        $selfUrl = api_get_self();
10573
        $courseIdReq = api_get_cidreq();
10574
        $course = api_get_course_info();
10575
        $userInfo = api_get_user_info();
10576
10577
        $course_id = $course['real_id'];
10578
        $tbl_link = Database::get_course_table(TABLE_LINK);
10579
        $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
10580
        $moveEverywhereIcon = Display::return_icon(
10581
            'move_everywhere.png',
10582
            get_lang('Move'),
10583
            [],
10584
            ICON_SIZE_TINY
10585
        );
10586
10587
        $session_id = api_get_session_id();
10588
        $condition_session = api_get_session_condition(
10589
            $session_id,
10590
            true,
10591
            true,
10592
            'link.session_id'
10593
        );
10594
10595
        $sql = "SELECT
10596
                    link.id as link_id,
10597
                    link.title as link_title,
10598
                    link.session_id as link_session_id,
10599
                    link.category_id as category_id,
10600
                    link_category.category_title as category_title
10601
                FROM $tbl_link as link
10602
                LEFT JOIN $linkCategoryTable as link_category
10603
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
10604
                WHERE link.c_id = $course_id $condition_session
10605
                ORDER BY link_category.category_title ASC, link.title ASC";
10606
        $result = Database::query($sql);
10607
        $categorizedLinks = [];
10608
        $categories = [];
10609
10610
        while ($link = Database::fetch_array($result)) {
10611
            if (!$link['category_id']) {
10612
                $link['category_title'] = get_lang('Uncategorized');
10613
            }
10614
            $categories[$link['category_id']] = $link['category_title'];
10615
            $categorizedLinks[$link['category_id']][$link['link_id']] = $link;
10616
        }
10617
10618
        $linksHtmlCode =
10619
            '<script>
10620
            function toggle_tool(tool, id) {
10621
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
10622
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
10623
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10624
                } else {
10625
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
10626
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10627
                }
10628
            }
10629
        </script>
10630
10631
        <ul class="lp_resource">
10632
            <li class="lp_resource_element">
10633
                '.Display::return_icon('linksnew.gif').'
10634
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('LinkAdd').'">'.
10635
                get_lang('LinkAdd').'
10636
                </a>
10637
            </li>';
10638
10639
        foreach ($categorizedLinks as $categoryId => $links) {
10640
            $linkNodes = null;
10641
            foreach ($links as $key => $linkInfo) {
10642
                $title = $linkInfo['link_title'];
10643
                $linkSessionId = $linkInfo['link_session_id'];
10644
10645
                $link = Display::url(
10646
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10647
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
10648
                    ['target' => '_blank']
10649
                );
10650
10651
                if (api_get_item_visibility($course, TOOL_LINK, $key, $session_id) != 2) {
10652
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
10653
                    $linkNodes .=
10654
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
10655
                        <a class="moved" href="#">'.
10656
                            $moveEverywhereIcon.
10657
                        '</a>
10658
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
10659
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
10660
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
10661
                        Security::remove_XSS($title).$sessionStar.$link.
10662
                        '</a>
10663
                    </li>';
10664
                }
10665
            }
10666
            $linksHtmlCode .=
10667
                '<li>
10668
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
10669
                    <img src="'.Display::returnIconPath('add.gif').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
10670
                    align="absbottom" />
10671
                </a>
10672
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
10673
            </li>
10674
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
10675
        }
10676
        $linksHtmlCode .= '</ul>';
10677
10678
        return $linksHtmlCode;
10679
    }
10680
10681
    /**
10682
     * Creates a list with all the student publications in it.
10683
     *
10684
     * @return string
10685
     */
10686
    public function get_student_publications()
10687
    {
10688
        $return = '<ul class="lp_resource">';
10689
        $return .= '<li class="lp_resource_element">';
10690
        $return .= Display::return_icon('works_new.gif');
10691
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
10692
            get_lang('AddAssignmentPage').'</a>';
10693
        $return .= '</li>';
10694
10695
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
10696
        $works = getWorkListTeacher(0, 100, null, null, null);
10697
        if (!empty($works)) {
10698
            foreach ($works as $work) {
10699
                $link = Display::url(
10700
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10701
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
10702
                    ['target' => '_blank']
10703
                );
10704
10705
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
10706
                $return .= '<a class="moved" href="#">';
10707
                $return .= Display::return_icon(
10708
                    'move_everywhere.png',
10709
                    get_lang('Move'),
10710
                    [],
10711
                    ICON_SIZE_TINY
10712
                );
10713
                $return .= '</a> ';
10714
10715
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
10716
                $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.'">'.
10717
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
10718
                </a>';
10719
10720
                $return .= '</li>';
10721
            }
10722
        }
10723
10724
        $return .= '</ul>';
10725
10726
        return $return;
10727
    }
10728
10729
    /**
10730
     * Creates a list with all the forums in it.
10731
     *
10732
     * @return string
10733
     */
10734
    public function get_forums()
10735
    {
10736
        require_once '../forum/forumfunction.inc.php';
10737
10738
        $forumCategories = get_forum_categories();
10739
        $forumsInNoCategory = get_forums_in_category(0);
10740
        if (!empty($forumsInNoCategory)) {
10741
            $forumCategories = array_merge(
10742
                $forumCategories,
10743
                [
10744
                    [
10745
                        'cat_id' => 0,
10746
                        'session_id' => 0,
10747
                        'visibility' => 1,
10748
                        'cat_comment' => null,
10749
                    ],
10750
                ]
10751
            );
10752
        }
10753
10754
        $forumList = get_forums();
10755
        $a_forums = [];
10756
        foreach ($forumCategories as $forumCategory) {
10757
            // The forums in this category.
10758
            $forumsInCategory = get_forums_in_category($forumCategory['cat_id']);
10759
            if (!empty($forumsInCategory)) {
10760
                foreach ($forumList as $forum) {
10761
                    if (isset($forum['forum_category']) &&
10762
                        $forum['forum_category'] == $forumCategory['cat_id']
10763
                    ) {
10764
                        $a_forums[] = $forum;
10765
                    }
10766
                }
10767
            }
10768
        }
10769
10770
        $return = '<ul class="lp_resource">';
10771
10772
        // First add link
10773
        $return .= '<li class="lp_resource_element">';
10774
        $return .= Display::return_icon('new_forum.png');
10775
        $return .= Display::url(
10776
            get_lang('CreateANewForum'),
10777
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
10778
                'action' => 'add',
10779
                'content' => 'forum',
10780
                'lp_id' => $this->lp_id,
10781
            ]),
10782
            ['title' => get_lang('CreateANewForum')]
10783
        );
10784
        $return .= '</li>';
10785
10786
        $return .= '<script>
10787
            function toggle_forum(forum_id) {
10788
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
10789
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
10790
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10791
                } else {
10792
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
10793
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10794
                }
10795
            }
10796
        </script>';
10797
10798
        foreach ($a_forums as $forum) {
10799
            if (!empty($forum['forum_id'])) {
10800
                $link = Display::url(
10801
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10802
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forum['forum_id'],
10803
                    ['target' => '_blank']
10804
                );
10805
10806
                $return .= '<li class="lp_resource_element" data_id="'.$forum['forum_id'].'" data_type="'.TOOL_FORUM.'" title="'.$forum['forum_title'].'" >';
10807
                $return .= '<a class="moved" href="#">';
10808
                $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10809
                $return .= ' </a>';
10810
                $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
10811
                $return .= '<a onclick="javascript:toggle_forum('.$forum['forum_id'].');" style="cursor:hand; vertical-align:middle">
10812
                                <img src="'.Display::returnIconPath('add.gif').'" id="forum_'.$forum['forum_id'].'_opener" align="absbottom" />
10813
                            </a>
10814
                            <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">'.
10815
                    Security::remove_XSS($forum['forum_title']).' '.$link.'</a>';
10816
10817
                $return .= '</li>';
10818
10819
                $return .= '<div style="display:none" id="forum_'.$forum['forum_id'].'_content">';
10820
                $a_threads = get_threads($forum['forum_id']);
10821
                if (is_array($a_threads)) {
10822
                    foreach ($a_threads as $thread) {
10823
                        $link = Display::url(
10824
                            Display::return_icon('preview_view.png', get_lang('Preview')),
10825
                            api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forum['forum_id'].'&thread='.$thread['thread_id'],
10826
                            ['target' => '_blank']
10827
                        );
10828
10829
                        $return .= '<li class="lp_resource_element" data_id="'.$thread['thread_id'].'" data_type="'.TOOL_THREAD.'" title="'.$thread['thread_title'].'" >';
10830
                        $return .= '&nbsp;<a class="moved" href="#">';
10831
                        $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10832
                        $return .= ' </a>';
10833
                        $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
10834
                        $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.'">'.
10835
                            Security::remove_XSS($thread['thread_title']).' '.$link.'</a>';
10836
                        $return .= '</li>';
10837
                    }
10838
                }
10839
                $return .= '</div>';
10840
            }
10841
        }
10842
        $return .= '</ul>';
10843
10844
        return $return;
10845
    }
10846
10847
    /**
10848
     * // TODO: The output encoding should be equal to the system encoding.
10849
     *
10850
     * Exports the learning path as a SCORM package. This is the main function that
10851
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
10852
     * whole thing and returns the zip.
10853
     *
10854
     * This method needs to be called in PHP5, as it will fail with non-adequate
10855
     * XML package (like the ones for PHP4), and it is *not* a static method, so
10856
     * you need to call it on a learnpath object.
10857
     *
10858
     * @TODO The method might be redefined later on in the scorm class itself to avoid
10859
     * creating a SCORM structure if there is one already. However, if the initial SCORM
10860
     * path has been modified, it should use the generic method here below.
10861
     *
10862
     * @return string Returns the zip package string, or null if error
10863
     */
10864
    public function scormExport()
10865
    {
10866
        api_set_more_memory_and_time_limits();
10867
10868
        $_course = api_get_course_info();
10869
        $course_id = $_course['real_id'];
10870
        // Create the zip handler (this will remain available throughout the method).
10871
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
10872
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
10873
        $temp_dir_short = uniqid('scorm_export', true);
10874
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
10875
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
10876
        $zip_folder = new PclZip($temp_zip_file);
10877
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
10878
        $root_path = $main_path = api_get_path(SYS_PATH);
10879
        $files_cleanup = [];
10880
10881
        // Place to temporarily stash the zip file.
10882
        // create the temp dir if it doesn't exist
10883
        // or do a cleanup before creating the zip file.
10884
        if (!is_dir($temp_zip_dir)) {
10885
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
10886
        } else {
10887
            // Cleanup: Check the temp dir for old files and delete them.
10888
            $handle = opendir($temp_zip_dir);
10889
            while (false !== ($file = readdir($handle))) {
10890
                if ($file != '.' && $file != '..') {
10891
                    unlink("$temp_zip_dir/$file");
10892
                }
10893
            }
10894
            closedir($handle);
10895
        }
10896
        $zip_files = $zip_files_abs = $zip_files_dist = [];
10897
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
10898
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
10899
        ) {
10900
            // Remove the possible . at the end of the path.
10901
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
10902
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
10903
            mkdir(
10904
                $dest_path_to_scorm_folder,
10905
                api_get_permissions_for_new_directories(),
10906
                true
10907
            );
10908
            copyr(
10909
                $current_course_path.'/scorm/'.$this->path,
10910
                $dest_path_to_scorm_folder,
10911
                ['imsmanifest'],
10912
                $zip_files
10913
            );
10914
        }
10915
10916
        // Build a dummy imsmanifest structure.
10917
        // Do not add to the zip yet (we still need it).
10918
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
10919
        // Aggregation Model official document, section "2.3 Content Packaging".
10920
        // We are going to build a UTF-8 encoded manifest.
10921
        // Later we will recode it to the desired (and supported) encoding.
10922
        $xmldoc = new DOMDocument('1.0');
10923
        $root = $xmldoc->createElement('manifest');
10924
        $root->setAttribute('identifier', 'SingleCourseManifest');
10925
        $root->setAttribute('version', '1.1');
10926
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
10927
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
10928
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
10929
        $root->setAttribute(
10930
            'xsi:schemaLocation',
10931
            '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'
10932
        );
10933
        // Build mandatory sub-root container elements.
10934
        $metadata = $xmldoc->createElement('metadata');
10935
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
10936
        $metadata->appendChild($md_schema);
10937
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
10938
        $metadata->appendChild($md_schemaversion);
10939
        $root->appendChild($metadata);
10940
10941
        $organizations = $xmldoc->createElement('organizations');
10942
        $resources = $xmldoc->createElement('resources');
10943
10944
        // Build the only organization we will use in building our learnpaths.
10945
        $organizations->setAttribute('default', 'chamilo_scorm_export');
10946
        $organization = $xmldoc->createElement('organization');
10947
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
10948
        // To set the title of the SCORM entity (=organization), we take the name given
10949
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
10950
        // learning path charset) as it is the encoding that defines how it is stored
10951
        // in the database. Then we convert it to HTML entities again as the "&" character
10952
        // alone is not authorized in XML (must be &amp;).
10953
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
10954
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
10955
        $organization->appendChild($org_title);
10956
        $folder_name = 'document';
10957
10958
        // Removes the learning_path/scorm_folder path when exporting see #4841
10959
        $path_to_remove = '';
10960
        $path_to_replace = '';
10961
        $result = $this->generate_lp_folder($_course);
10962
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
10963
            $path_to_remove = 'document'.$result['dir'];
10964
            $path_to_replace = $folder_name.'/';
10965
        }
10966
10967
        // Fixes chamilo scorm exports
10968
        if ($this->ref === 'chamilo_scorm_export') {
10969
            $path_to_remove = 'scorm/'.$this->path.'/document/';
10970
        }
10971
10972
        // For each element, add it to the imsmanifest structure, then add it to the zip.
10973
        $link_updates = [];
10974
        $links_to_create = [];
10975
        foreach ($this->ordered_items as $index => $itemId) {
10976
            /** @var learnpathItem $item */
10977
            $item = $this->items[$itemId];
10978
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
10979
                // Get included documents from this item.
10980
                if ($item->type === 'sco') {
10981
                    $inc_docs = $item->get_resources_from_source(
10982
                        null,
10983
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
10984
                    );
10985
                } else {
10986
                    $inc_docs = $item->get_resources_from_source();
10987
                }
10988
10989
                // Give a child element <item> to the <organization> element.
10990
                $my_item_id = $item->get_id();
10991
                $my_item = $xmldoc->createElement('item');
10992
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
10993
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
10994
                $my_item->setAttribute('isvisible', 'true');
10995
                // Give a child element <title> to the <item> element.
10996
                $my_title = $xmldoc->createElement(
10997
                    'title',
10998
                    htmlspecialchars(
10999
                        api_utf8_encode($item->get_title()),
11000
                        ENT_QUOTES,
11001
                        'UTF-8'
11002
                    )
11003
                );
11004
                $my_item->appendChild($my_title);
11005
                // Give a child element <adlcp:prerequisites> to the <item> element.
11006
                $my_prereqs = $xmldoc->createElement(
11007
                    'adlcp:prerequisites',
11008
                    $this->get_scorm_prereq_string($my_item_id)
11009
                );
11010
                $my_prereqs->setAttribute('type', 'aicc_script');
11011
                $my_item->appendChild($my_prereqs);
11012
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
11013
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
11014
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
11015
                //$xmldoc->createElement('adlcp:timelimitaction','');
11016
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
11017
                //$xmldoc->createElement('adlcp:datafromlms','');
11018
                // Give a child element <adlcp:masteryscore> to the <item> element.
11019
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11020
                $my_item->appendChild($my_masteryscore);
11021
11022
                // Attach this item to the organization element or hits parent if there is one.
11023
                if (!empty($item->parent) && $item->parent != 0) {
11024
                    $children = $organization->childNodes;
11025
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11026
                    if (is_object($possible_parent)) {
11027
                        $possible_parent->appendChild($my_item);
11028
                    } else {
11029
                        if ($this->debug > 0) {
11030
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
11031
                        }
11032
                    }
11033
                } else {
11034
                    if ($this->debug > 0) {
11035
                        error_log('No parent');
11036
                    }
11037
                    $organization->appendChild($my_item);
11038
                }
11039
11040
                // Get the path of the file(s) from the course directory root.
11041
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11042
                $my_xml_file_path = $my_file_path;
11043
                if (!empty($path_to_remove)) {
11044
                    // From docs
11045
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
11046
11047
                    // From quiz
11048
                    if ($this->ref === 'chamilo_scorm_export') {
11049
                        $path_to_remove = 'scorm/'.$this->path.'/';
11050
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
11051
                    }
11052
                }
11053
11054
                $my_sub_dir = dirname($my_file_path);
11055
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11056
                $my_xml_sub_dir = $my_sub_dir;
11057
                // Give a <resource> child to the <resources> element
11058
                $my_resource = $xmldoc->createElement('resource');
11059
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11060
                $my_resource->setAttribute('type', 'webcontent');
11061
                $my_resource->setAttribute('href', $my_xml_file_path);
11062
                // adlcp:scormtype can be either 'sco' or 'asset'.
11063
                if ($item->type === 'sco') {
11064
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
11065
                } else {
11066
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
11067
                }
11068
                // xml:base is the base directory to find the files declared in this resource.
11069
                $my_resource->setAttribute('xml:base', '');
11070
                // Give a <file> child to the <resource> element.
11071
                $my_file = $xmldoc->createElement('file');
11072
                $my_file->setAttribute('href', $my_xml_file_path);
11073
                $my_resource->appendChild($my_file);
11074
11075
                // Dependency to other files - not yet supported.
11076
                $i = 1;
11077
                if ($inc_docs) {
11078
                    foreach ($inc_docs as $doc_info) {
11079
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
11080
                            continue;
11081
                        }
11082
                        $my_dep = $xmldoc->createElement('resource');
11083
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11084
                        $my_dep->setAttribute('identifier', $res_id);
11085
                        $my_dep->setAttribute('type', 'webcontent');
11086
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
11087
                        $my_dep_file = $xmldoc->createElement('file');
11088
                        // Check type of URL.
11089
                        if ($doc_info[1] == 'remote') {
11090
                            // Remote file. Save url as is.
11091
                            $my_dep_file->setAttribute('href', $doc_info[0]);
11092
                            $my_dep->setAttribute('xml:base', '');
11093
                        } elseif ($doc_info[1] === 'local') {
11094
                            switch ($doc_info[2]) {
11095
                                case 'url':
11096
                                    // Local URL - save path as url for now, don't zip file.
11097
                                    $abs_path = api_get_path(SYS_PATH).
11098
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11099
                                    $current_dir = dirname($abs_path);
11100
                                    $current_dir = str_replace('\\', '/', $current_dir);
11101
                                    $file_path = realpath($abs_path);
11102
                                    $file_path = str_replace('\\', '/', $file_path);
11103
                                    $my_dep_file->setAttribute('href', $file_path);
11104
                                    $my_dep->setAttribute('xml:base', '');
11105
                                    if (strstr($file_path, $main_path) !== false) {
11106
                                        // The calculated real path is really inside Chamilo's root path.
11107
                                        // Reduce file path to what's under the DocumentRoot.
11108
                                        $replace = $file_path;
11109
                                        $file_path = substr($file_path, strlen($root_path) - 1);
11110
                                        $destinationFile = $file_path;
11111
11112
                                        if (strstr($file_path, 'upload/users') !== false) {
11113
                                            $pos = strpos($file_path, 'my_files/');
11114
                                            if ($pos !== false) {
11115
                                                $onlyDirectory = str_replace(
11116
                                                    'upload/users/',
11117
                                                    '',
11118
                                                    substr($file_path, $pos, strlen($file_path))
11119
                                                );
11120
                                            }
11121
                                            $replace = $onlyDirectory;
11122
                                            $destinationFile = $replace;
11123
                                        }
11124
                                        $zip_files_abs[] = $file_path;
11125
                                        $link_updates[$my_file_path][] = [
11126
                                            'orig' => $doc_info[0],
11127
                                            'dest' => $destinationFile,
11128
                                            'replace' => $replace,
11129
                                        ];
11130
                                        $my_dep_file->setAttribute('href', $file_path);
11131
                                        $my_dep->setAttribute('xml:base', '');
11132
                                    } elseif (empty($file_path)) {
11133
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11134
                                        $file_path = str_replace('//', '/', $file_path);
11135
                                        if (file_exists($file_path)) {
11136
                                            // We get the relative path.
11137
                                            $file_path = substr($file_path, strlen($current_dir));
11138
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
11139
                                            $link_updates[$my_file_path][] = [
11140
                                                'orig' => $doc_info[0],
11141
                                                'dest' => $file_path,
11142
                                            ];
11143
                                            $my_dep_file->setAttribute('href', $file_path);
11144
                                            $my_dep->setAttribute('xml:base', '');
11145
                                        }
11146
                                    }
11147
                                    break;
11148
                                case 'abs':
11149
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11150
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11151
                                    $my_dep->setAttribute('xml:base', '');
11152
11153
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
11154
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
11155
                                    $abs_img_path_without_subdir = $doc_info[0];
11156
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
11157
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
11158
                                    if ($pos === 0) {
11159
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
11160
                                    }
11161
11162
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
11163
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
11164
11165
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
11166
                                    $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
11167
                                    // Check if the current document is in that path.
11168
                                    if (strstr($file_path, $cur_path) !== false) {
11169
                                        $destinationFile = substr($file_path, strlen($cur_path));
11170
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
11171
11172
                                        $fileToTest = $cur_path.$my_file_path;
11173
                                        if (!empty($path_to_remove)) {
11174
                                            $fileToTest = str_replace(
11175
                                                $path_to_remove.'/',
11176
                                                $path_to_replace,
11177
                                                $cur_path.$my_file_path
11178
                                            );
11179
                                        }
11180
11181
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
11182
11183
                                        // Put the current document in the zip (this array is the array
11184
                                        // that will manage documents already in the course folder - relative).
11185
                                        $zip_files[] = $filePathNoCoursePart;
11186
                                        // Update the links to the current document in the
11187
                                        // containing document (make them relative).
11188
                                        $link_updates[$my_file_path][] = [
11189
                                            'orig' => $doc_info[0],
11190
                                            'dest' => $destinationFile,
11191
                                            'replace' => $relative_path,
11192
                                        ];
11193
11194
                                        $my_dep_file->setAttribute('href', $file_path);
11195
                                        $my_dep->setAttribute('xml:base', '');
11196
                                    } elseif (strstr($file_path, $main_path) !== false) {
11197
                                        // The calculated real path is really inside Chamilo's root path.
11198
                                        // Reduce file path to what's under the DocumentRoot.
11199
                                        $file_path = substr($file_path, strlen($root_path));
11200
                                        $zip_files_abs[] = $file_path;
11201
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11202
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11203
                                        $my_dep->setAttribute('xml:base', '');
11204
                                    } elseif (empty($file_path)) {
11205
                                        // Probably this is an image inside "/main" directory
11206
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
11207
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11208
11209
                                        if (file_exists($file_path)) {
11210
                                            if (strstr($file_path, 'main/default_course_document') !== false) {
11211
                                                // We get the relative path.
11212
                                                $pos = strpos($file_path, 'main/default_course_document/');
11213
                                                if ($pos !== false) {
11214
                                                    $onlyDirectory = str_replace(
11215
                                                        'main/default_course_document/',
11216
                                                        '',
11217
                                                        substr($file_path, $pos, strlen($file_path))
11218
                                                    );
11219
                                                }
11220
11221
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
11222
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
11223
                                                $zip_files_abs[] = $fileAbs;
11224
                                                $link_updates[$my_file_path][] = [
11225
                                                    'orig' => $doc_info[0],
11226
                                                    'dest' => $destinationFile,
11227
                                                ];
11228
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11229
                                                $my_dep->setAttribute('xml:base', '');
11230
                                            }
11231
                                        }
11232
                                    }
11233
                                    break;
11234
                                case 'rel':
11235
                                    // Path relative to the current document.
11236
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
11237
                                    if (substr($doc_info[0], 0, 2) === '..') {
11238
                                        // Relative path going up.
11239
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11240
                                        $current_dir = str_replace('\\', '/', $current_dir);
11241
                                        $file_path = realpath($current_dir.$doc_info[0]);
11242
                                        $file_path = str_replace('\\', '/', $file_path);
11243
                                        if (strstr($file_path, $main_path) !== false) {
11244
                                            // The calculated real path is really inside Chamilo's root path.
11245
                                            // Reduce file path to what's under the DocumentRoot.
11246
                                            $file_path = substr($file_path, strlen($root_path));
11247
                                            $zip_files_abs[] = $file_path;
11248
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11249
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11250
                                            $my_dep->setAttribute('xml:base', '');
11251
                                        }
11252
                                    } else {
11253
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11254
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
11255
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11256
                                    }
11257
                                    break;
11258
                                default:
11259
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11260
                                    $my_dep->setAttribute('xml:base', '');
11261
                                    break;
11262
                            }
11263
                        }
11264
                        $my_dep->appendChild($my_dep_file);
11265
                        $resources->appendChild($my_dep);
11266
                        $dependency = $xmldoc->createElement('dependency');
11267
                        $dependency->setAttribute('identifierref', $res_id);
11268
                        $my_resource->appendChild($dependency);
11269
                        $i++;
11270
                    }
11271
                }
11272
                $resources->appendChild($my_resource);
11273
                $zip_files[] = $my_file_path;
11274
            } else {
11275
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
11276
                switch ($item->type) {
11277
                    case TOOL_LINK:
11278
                        $my_item = $xmldoc->createElement('item');
11279
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11280
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11281
                        $my_item->setAttribute('isvisible', 'true');
11282
                        // Give a child element <title> to the <item> element.
11283
                        $my_title = $xmldoc->createElement(
11284
                            'title',
11285
                            htmlspecialchars(
11286
                                api_utf8_encode($item->get_title()),
11287
                                ENT_QUOTES,
11288
                                'UTF-8'
11289
                            )
11290
                        );
11291
                        $my_item->appendChild($my_title);
11292
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11293
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11294
                        $my_prereqs->setAttribute('type', 'aicc_script');
11295
                        $my_item->appendChild($my_prereqs);
11296
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
11297
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
11298
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
11299
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
11300
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
11301
                        //$xmldoc->createElement('adlcp:datafromlms', '');
11302
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11303
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11304
                        $my_item->appendChild($my_masteryscore);
11305
11306
                        // Attach this item to the organization element or its parent if there is one.
11307
                        if (!empty($item->parent) && $item->parent != 0) {
11308
                            $children = $organization->childNodes;
11309
                            for ($i = 0; $i < $children->length; $i++) {
11310
                                $item_temp = $children->item($i);
11311
                                if ($item_temp->nodeName == 'item') {
11312
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
11313
                                        $item_temp->appendChild($my_item);
11314
                                    }
11315
                                }
11316
                            }
11317
                        } else {
11318
                            $organization->appendChild($my_item);
11319
                        }
11320
11321
                        $my_file_path = 'link_'.$item->get_id().'.html';
11322
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
11323
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
11324
                        $rs = Database::query($sql);
11325
                        if ($link = Database::fetch_array($rs)) {
11326
                            $url = $link['url'];
11327
                            $title = stripslashes($link['title']);
11328
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
11329
                            $my_xml_file_path = $my_file_path;
11330
                            $my_sub_dir = dirname($my_file_path);
11331
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11332
                            $my_xml_sub_dir = $my_sub_dir;
11333
                            // Give a <resource> child to the <resources> element.
11334
                            $my_resource = $xmldoc->createElement('resource');
11335
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11336
                            $my_resource->setAttribute('type', 'webcontent');
11337
                            $my_resource->setAttribute('href', $my_xml_file_path);
11338
                            // adlcp:scormtype can be either 'sco' or 'asset'.
11339
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
11340
                            // xml:base is the base directory to find the files declared in this resource.
11341
                            $my_resource->setAttribute('xml:base', '');
11342
                            // give a <file> child to the <resource> element.
11343
                            $my_file = $xmldoc->createElement('file');
11344
                            $my_file->setAttribute('href', $my_xml_file_path);
11345
                            $my_resource->appendChild($my_file);
11346
                            $resources->appendChild($my_resource);
11347
                        }
11348
                        break;
11349
                    case TOOL_QUIZ:
11350
                        $exe_id = $item->path;
11351
                        // Should be using ref when everything will be cleaned up in this regard.
11352
                        $exe = new Exercise();
11353
                        $exe->read($exe_id);
11354
                        $my_item = $xmldoc->createElement('item');
11355
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11356
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11357
                        $my_item->setAttribute('isvisible', 'true');
11358
                        // Give a child element <title> to the <item> element.
11359
                        $my_title = $xmldoc->createElement(
11360
                            'title',
11361
                            htmlspecialchars(
11362
                                api_utf8_encode($item->get_title()),
11363
                                ENT_QUOTES,
11364
                                'UTF-8'
11365
                            )
11366
                        );
11367
                        $my_item->appendChild($my_title);
11368
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
11369
                        $my_item->appendChild($my_max_score);
11370
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11371
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11372
                        $my_prereqs->setAttribute('type', 'aicc_script');
11373
                        $my_item->appendChild($my_prereqs);
11374
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11375
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11376
                        $my_item->appendChild($my_masteryscore);
11377
11378
                        // Attach this item to the organization element or hits parent if there is one.
11379
                        if (!empty($item->parent) && $item->parent != 0) {
11380
                            $children = $organization->childNodes;
11381
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11382
                            if ($possible_parent) {
11383
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
11384
                                    $possible_parent->appendChild($my_item);
11385
                                }
11386
                            }
11387
                        } else {
11388
                            $organization->appendChild($my_item);
11389
                        }
11390
11391
                        // Get the path of the file(s) from the course directory root
11392
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11393
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
11394
                        // Write the contents of the exported exercise into a (big) html file
11395
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
11396
                        $scormExercise = new ScormExercise($exe, true);
11397
                        $contents = $scormExercise->export();
11398
11399
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
11400
                        $res = file_put_contents($tmp_file_path, $contents);
11401
                        if ($res === false) {
11402
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
11403
                        }
11404
                        $files_cleanup[] = $tmp_file_path;
11405
                        $my_xml_file_path = $my_file_path;
11406
                        $my_sub_dir = dirname($my_file_path);
11407
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11408
                        $my_xml_sub_dir = $my_sub_dir;
11409
                        // Give a <resource> child to the <resources> element.
11410
                        $my_resource = $xmldoc->createElement('resource');
11411
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11412
                        $my_resource->setAttribute('type', 'webcontent');
11413
                        $my_resource->setAttribute('href', $my_xml_file_path);
11414
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11415
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
11416
                        // xml:base is the base directory to find the files declared in this resource.
11417
                        $my_resource->setAttribute('xml:base', '');
11418
                        // Give a <file> child to the <resource> element.
11419
                        $my_file = $xmldoc->createElement('file');
11420
                        $my_file->setAttribute('href', $my_xml_file_path);
11421
                        $my_resource->appendChild($my_file);
11422
11423
                        // Get included docs.
11424
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
11425
11426
                        // Dependency to other files - not yet supported.
11427
                        $i = 1;
11428
                        foreach ($inc_docs as $doc_info) {
11429
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
11430
                                continue;
11431
                            }
11432
                            $my_dep = $xmldoc->createElement('resource');
11433
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11434
                            $my_dep->setAttribute('identifier', $res_id);
11435
                            $my_dep->setAttribute('type', 'webcontent');
11436
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
11437
                            $my_dep_file = $xmldoc->createElement('file');
11438
                            // Check type of URL.
11439
                            if ($doc_info[1] == 'remote') {
11440
                                // Remote file. Save url as is.
11441
                                $my_dep_file->setAttribute('href', $doc_info[0]);
11442
                                $my_dep->setAttribute('xml:base', '');
11443
                            } elseif ($doc_info[1] == 'local') {
11444
                                switch ($doc_info[2]) {
11445
                                    case 'url': // Local URL - save path as url for now, don't zip file.
11446
                                        // Save file but as local file (retrieve from URL).
11447
                                        $abs_path = api_get_path(SYS_PATH).
11448
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11449
                                        $current_dir = dirname($abs_path);
11450
                                        $current_dir = str_replace('\\', '/', $current_dir);
11451
                                        $file_path = realpath($abs_path);
11452
                                        $file_path = str_replace('\\', '/', $file_path);
11453
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11454
                                        $my_dep->setAttribute('xml:base', '');
11455
                                        if (strstr($file_path, $main_path) !== false) {
11456
                                            // The calculated real path is really inside the chamilo root path.
11457
                                            // Reduce file path to what's under the DocumentRoot.
11458
                                            $file_path = substr($file_path, strlen($root_path));
11459
                                            $zip_files_abs[] = $file_path;
11460
                                            $link_updates[$my_file_path][] = [
11461
                                                'orig' => $doc_info[0],
11462
                                                'dest' => 'document/'.$file_path,
11463
                                            ];
11464
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11465
                                            $my_dep->setAttribute('xml:base', '');
11466
                                        } elseif (empty($file_path)) {
11467
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11468
                                            $file_path = str_replace('//', '/', $file_path);
11469
                                            if (file_exists($file_path)) {
11470
                                                $file_path = substr($file_path, strlen($current_dir));
11471
                                                // We get the relative path.
11472
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11473
                                                $link_updates[$my_file_path][] = [
11474
                                                    'orig' => $doc_info[0],
11475
                                                    'dest' => 'document/'.$file_path,
11476
                                                ];
11477
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11478
                                                $my_dep->setAttribute('xml:base', '');
11479
                                            }
11480
                                        }
11481
                                        break;
11482
                                    case 'abs':
11483
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11484
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11485
                                        $current_dir = str_replace('\\', '/', $current_dir);
11486
                                        $file_path = realpath($doc_info[0]);
11487
                                        $file_path = str_replace('\\', '/', $file_path);
11488
                                        $my_dep_file->setAttribute('href', $file_path);
11489
                                        $my_dep->setAttribute('xml:base', '');
11490
11491
                                        if (strstr($file_path, $main_path) !== false) {
11492
                                            // The calculated real path is really inside the chamilo root path.
11493
                                            // Reduce file path to what's under the DocumentRoot.
11494
                                            $file_path = substr($file_path, strlen($root_path));
11495
                                            $zip_files_abs[] = $file_path;
11496
                                            $link_updates[$my_file_path][] = [
11497
                                                'orig' => $doc_info[0],
11498
                                                'dest' => $file_path,
11499
                                            ];
11500
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11501
                                            $my_dep->setAttribute('xml:base', '');
11502
                                        } elseif (empty($file_path)) {
11503
                                            $docSysPartPath = str_replace(
11504
                                                api_get_path(REL_COURSE_PATH),
11505
                                                '',
11506
                                                $doc_info[0]
11507
                                            );
11508
11509
                                            $docSysPartPathNoCourseCode = str_replace(
11510
                                                $_course['directory'].'/',
11511
                                                '',
11512
                                                $docSysPartPath
11513
                                            );
11514
11515
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
11516
                                            if (file_exists($docSysPath)) {
11517
                                                $file_path = $docSysPartPathNoCourseCode;
11518
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11519
                                                $link_updates[$my_file_path][] = [
11520
                                                    'orig' => $doc_info[0],
11521
                                                    'dest' => $file_path,
11522
                                                ];
11523
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11524
                                                $my_dep->setAttribute('xml:base', '');
11525
                                            }
11526
                                        }
11527
                                        break;
11528
                                    case 'rel':
11529
                                        // Path relative to the current document. Save xml:base as current document's
11530
                                        // directory and save file in zip as subdir.file_path
11531
                                        if (substr($doc_info[0], 0, 2) === '..') {
11532
                                            // Relative path going up.
11533
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11534
                                            $current_dir = str_replace('\\', '/', $current_dir);
11535
                                            $file_path = realpath($current_dir.$doc_info[0]);
11536
                                            $file_path = str_replace('\\', '/', $file_path);
11537
                                            if (strstr($file_path, $main_path) !== false) {
11538
                                                // The calculated real path is really inside Chamilo's root path.
11539
                                                // Reduce file path to what's under the DocumentRoot.
11540
11541
                                                $file_path = substr($file_path, strlen($root_path));
11542
                                                $file_path_dest = $file_path;
11543
11544
                                                // File path is courses/CHAMILO/document/....
11545
                                                $info_file_path = explode('/', $file_path);
11546
                                                if ($info_file_path[0] == 'courses') {
11547
                                                    // Add character "/" in file path.
11548
                                                    $file_path_dest = 'document/'.$file_path;
11549
                                                }
11550
                                                $zip_files_abs[] = $file_path;
11551
11552
                                                $link_updates[$my_file_path][] = [
11553
                                                    'orig' => $doc_info[0],
11554
                                                    'dest' => $file_path_dest,
11555
                                                ];
11556
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11557
                                                $my_dep->setAttribute('xml:base', '');
11558
                                            }
11559
                                        } else {
11560
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11561
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
11562
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11563
                                        }
11564
                                        break;
11565
                                    default:
11566
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
11567
                                        $my_dep->setAttribute('xml:base', '');
11568
                                        break;
11569
                                }
11570
                            }
11571
                            $my_dep->appendChild($my_dep_file);
11572
                            $resources->appendChild($my_dep);
11573
                            $dependency = $xmldoc->createElement('dependency');
11574
                            $dependency->setAttribute('identifierref', $res_id);
11575
                            $my_resource->appendChild($dependency);
11576
                            $i++;
11577
                        }
11578
                        $resources->appendChild($my_resource);
11579
                        $zip_files[] = $my_file_path;
11580
                        break;
11581
                    default:
11582
                        // Get the path of the file(s) from the course directory root
11583
                        $my_file_path = 'non_exportable.html';
11584
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
11585
                        $my_xml_file_path = $my_file_path;
11586
                        $my_sub_dir = dirname($my_file_path);
11587
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11588
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
11589
                        $my_xml_sub_dir = $my_sub_dir;
11590
                        // Give a <resource> child to the <resources> element.
11591
                        $my_resource = $xmldoc->createElement('resource');
11592
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11593
                        $my_resource->setAttribute('type', 'webcontent');
11594
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
11595
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11596
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
11597
                        // xml:base is the base directory to find the files declared in this resource.
11598
                        $my_resource->setAttribute('xml:base', '');
11599
                        // Give a <file> child to the <resource> element.
11600
                        $my_file = $xmldoc->createElement('file');
11601
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
11602
                        $my_resource->appendChild($my_file);
11603
                        $resources->appendChild($my_resource);
11604
                        break;
11605
                }
11606
            }
11607
        }
11608
        $organizations->appendChild($organization);
11609
        $root->appendChild($organizations);
11610
        $root->appendChild($resources);
11611
        $xmldoc->appendChild($root);
11612
11613
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
11614
11615
        // then add the file to the zip, then destroy the file (this is done automatically).
11616
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
11617
        foreach ($zip_files as $file_path) {
11618
            if (empty($file_path)) {
11619
                continue;
11620
            }
11621
11622
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
11623
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
11624
11625
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
11626
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
11627
            }
11628
11629
            $this->create_path($dest_file);
11630
            @copy($filePath, $dest_file);
11631
11632
            // Check if the file needs a link update.
11633
            if (in_array($file_path, array_keys($link_updates))) {
11634
                $string = file_get_contents($dest_file);
11635
                unlink($dest_file);
11636
                foreach ($link_updates[$file_path] as $old_new) {
11637
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11638
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11639
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11640
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11641
                    if (substr($old_new['dest'], -3) === 'flv' &&
11642
                        substr($old_new['dest'], 0, 5) === 'main/'
11643
                    ) {
11644
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11645
                    } elseif (substr($old_new['dest'], -3) === 'flv' &&
11646
                        substr($old_new['dest'], 0, 6) === 'video/'
11647
                    ) {
11648
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
11649
                    }
11650
11651
                    // Fix to avoid problems with default_course_document
11652
                    if (strpos('main/default_course_document', $old_new['dest']) === false) {
11653
                        $newDestination = $old_new['dest'];
11654
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
11655
                            $newDestination = $old_new['replace'];
11656
                        }
11657
                    } else {
11658
                        $newDestination = str_replace('document/', '', $old_new['dest']);
11659
                    }
11660
                    $string = str_replace($old_new['orig'], $newDestination, $string);
11661
11662
                    // Add files inside the HTMLs
11663
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
11664
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
11665
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
11666
                        copy(
11667
                            $sys_course_path.$new_path,
11668
                            $destinationFile
11669
                        );
11670
                    }
11671
                }
11672
                file_put_contents($dest_file, $string);
11673
            }
11674
11675
            if (file_exists($filePath) && $copyAll) {
11676
                $extension = $this->get_extension($filePath);
11677
                if (in_array($extension, ['html', 'html'])) {
11678
                    $containerOrigin = dirname($filePath);
11679
                    $containerDestination = dirname($dest_file);
11680
11681
                    $finder = new Finder();
11682
                    $finder->files()->in($containerOrigin)
11683
                        ->notName('*_DELETED_*')
11684
                        ->exclude('share_folder')
11685
                        ->exclude('chat_files')
11686
                        ->exclude('certificates')
11687
                    ;
11688
11689
                    if (is_dir($containerOrigin) &&
11690
                        is_dir($containerDestination)
11691
                    ) {
11692
                        $fs = new Filesystem();
11693
                        $fs->mirror(
11694
                            $containerOrigin,
11695
                            $containerDestination,
11696
                            $finder
11697
                        );
11698
                    }
11699
                }
11700
            }
11701
        }
11702
11703
        foreach ($zip_files_abs as $file_path) {
11704
            if (empty($file_path)) {
11705
                continue;
11706
            }
11707
11708
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
11709
                continue;
11710
            }
11711
11712
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
11713
            if (strstr($file_path, 'upload/users') !== false) {
11714
                $pos = strpos($file_path, 'my_files/');
11715
                if ($pos !== false) {
11716
                    $onlyDirectory = str_replace(
11717
                        'upload/users/',
11718
                        '',
11719
                        substr($file_path, $pos, strlen($file_path))
11720
                    );
11721
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
11722
                }
11723
            }
11724
11725
            if (strstr($file_path, 'default_course_document/') !== false) {
11726
                $replace = str_replace('/main', '', $file_path);
11727
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
11728
            }
11729
11730
            if (empty($dest_file)) {
11731
                continue;
11732
            }
11733
11734
            $this->create_path($dest_file);
11735
            copy($main_path.$file_path, $dest_file);
11736
            // Check if the file needs a link update.
11737
            if (in_array($file_path, array_keys($link_updates))) {
11738
                $string = file_get_contents($dest_file);
11739
                unlink($dest_file);
11740
                foreach ($link_updates[$file_path] as $old_new) {
11741
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11742
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11743
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11744
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11745
                    if (substr($old_new['dest'], -3) == 'flv' &&
11746
                        substr($old_new['dest'], 0, 5) == 'main/'
11747
                    ) {
11748
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11749
                    }
11750
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
11751
                }
11752
                file_put_contents($dest_file, $string);
11753
            }
11754
        }
11755
11756
        if (is_array($links_to_create)) {
11757
            foreach ($links_to_create as $file => $link) {
11758
                $content = '<!DOCTYPE html><head>
11759
                            <meta charset="'.api_get_language_isocode().'" />
11760
                            <title>'.$link['title'].'</title>
11761
                            </head>
11762
                            <body dir="'.api_get_text_direction().'">
11763
                            <div style="text-align:center">
11764
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
11765
                            </body>
11766
                            </html>';
11767
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
11768
            }
11769
        }
11770
11771
        // Add non exportable message explanation.
11772
        $lang_not_exportable = get_lang('ThisItemIsNotExportable');
11773
        $file_content = '<!DOCTYPE html><head>
11774
                        <meta charset="'.api_get_language_isocode().'" />
11775
                        <title>'.$lang_not_exportable.'</title>
11776
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
11777
                        </head>
11778
                        <body dir="'.api_get_text_direction().'">';
11779
        $file_content .=
11780
            <<<EOD
11781
                    <style>
11782
            .error-message {
11783
                font-family: arial, verdana, helvetica, sans-serif;
11784
                border-width: 1px;
11785
                border-style: solid;
11786
                left: 50%;
11787
                margin: 10px auto;
11788
                min-height: 30px;
11789
                padding: 5px;
11790
                right: 50%;
11791
                width: 500px;
11792
                background-color: #FFD1D1;
11793
                border-color: #FF0000;
11794
                color: #000;
11795
            }
11796
        </style>
11797
    <body>
11798
        <div class="error-message">
11799
            $lang_not_exportable
11800
        </div>
11801
    </body>
11802
</html>
11803
EOD;
11804
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
11805
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
11806
        }
11807
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
11808
11809
        // Add the extra files that go along with a SCORM package.
11810
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
11811
11812
        $fs = new Filesystem();
11813
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
11814
11815
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
11816
        $manifest = @$xmldoc->saveXML();
11817
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
11818
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
11819
        $zip_folder->add(
11820
            $archivePath.'/'.$temp_dir_short,
11821
            PCLZIP_OPT_REMOVE_PATH,
11822
            $archivePath.'/'.$temp_dir_short.'/'
11823
        );
11824
11825
        // Clean possible temporary files.
11826
        foreach ($files_cleanup as $file) {
11827
            $res = unlink($file);
11828
            if ($res === false) {
11829
                error_log(
11830
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
11831
                    0
11832
                );
11833
            }
11834
        }
11835
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
11836
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
11837
    }
11838
11839
    /**
11840
     * @param int $lp_id
11841
     *
11842
     * @return bool
11843
     */
11844
    public function scorm_export_to_pdf($lp_id)
11845
    {
11846
        $lp_id = (int) $lp_id;
11847
        $files_to_export = [];
11848
11849
        $sessionId = api_get_session_id();
11850
        $course_data = api_get_course_info($this->cc);
11851
11852
        if (!empty($course_data)) {
11853
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
11854
            $list = self::get_flat_ordered_items_list($lp_id);
11855
            if (!empty($list)) {
11856
                foreach ($list as $item_id) {
11857
                    $item = $this->items[$item_id];
11858
                    switch ($item->type) {
11859
                        case 'document':
11860
                            // Getting documents from a LP with chamilo documents
11861
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
11862
                            // Try loading document from the base course.
11863
                            if (empty($file_data) && !empty($sessionId)) {
11864
                                $file_data = DocumentManager::get_document_data_by_id(
11865
                                    $item->path,
11866
                                    $this->cc,
11867
                                    false,
11868
                                    0
11869
                                );
11870
                            }
11871
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
11872
                            if (file_exists($file_path)) {
11873
                                $files_to_export[] = [
11874
                                    'title' => $item->get_title(),
11875
                                    'path' => $file_path,
11876
                                ];
11877
                            }
11878
                            break;
11879
                        case 'asset': //commes from a scorm package generated by chamilo
11880
                        case 'sco':
11881
                            $file_path = $scorm_path.'/'.$item->path;
11882
                            if (file_exists($file_path)) {
11883
                                $files_to_export[] = [
11884
                                    'title' => $item->get_title(),
11885
                                    'path' => $file_path,
11886
                                ];
11887
                            }
11888
                            break;
11889
                        case 'dir':
11890
                            $files_to_export[] = [
11891
                                'title' => $item->get_title(),
11892
                                'path' => null,
11893
                            ];
11894
                            break;
11895
                    }
11896
                }
11897
            }
11898
11899
            $pdf = new PDF();
11900
            $result = $pdf->html_to_pdf(
11901
                $files_to_export,
11902
                $this->name,
11903
                $this->cc,
11904
                true,
11905
                true,
11906
                true,
11907
                $this->get_name()
11908
            );
11909
11910
            return $result;
11911
        }
11912
11913
        return false;
11914
    }
11915
11916
    /**
11917
     * Temp function to be moved in main_api or the best place around for this.
11918
     * Creates a file path if it doesn't exist.
11919
     *
11920
     * @param string $path
11921
     */
11922
    public function create_path($path)
11923
    {
11924
        $path_bits = explode('/', dirname($path));
11925
11926
        // IS_WINDOWS_OS has been defined in main_api.lib.php
11927
        $path_built = IS_WINDOWS_OS ? '' : '/';
11928
        foreach ($path_bits as $bit) {
11929
            if (!empty($bit)) {
11930
                $new_path = $path_built.$bit;
11931
                if (is_dir($new_path)) {
11932
                    $path_built = $new_path.'/';
11933
                } else {
11934
                    mkdir($new_path, api_get_permissions_for_new_directories());
11935
                    $path_built = $new_path.'/';
11936
                }
11937
            }
11938
        }
11939
    }
11940
11941
    /**
11942
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
11943
     *
11944
     * @return bool The results of the unlink function, or false if there was no image to start with
11945
     */
11946
    public function delete_lp_image()
11947
    {
11948
        $img = $this->get_preview_image();
11949
        if ($img != '') {
11950
            $del_file = $this->get_preview_image_path(null, 'sys');
11951
            if (isset($del_file) && file_exists($del_file)) {
11952
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
11953
                if (file_exists($del_file_2)) {
11954
                    unlink($del_file_2);
11955
                }
11956
                $this->set_preview_image('');
11957
11958
                return @unlink($del_file);
11959
            }
11960
        }
11961
11962
        return false;
11963
    }
11964
11965
    /**
11966
     * Uploads an author image to the upload/learning_path/images directory.
11967
     *
11968
     * @param array    The image array, coming from the $_FILES superglobal
11969
     *
11970
     * @return bool True on success, false on error
11971
     */
11972
    public function upload_image($image_array)
11973
    {
11974
        if (!empty($image_array['name'])) {
11975
            $upload_ok = process_uploaded_file($image_array);
11976
            $has_attachment = true;
11977
        }
11978
11979
        if ($upload_ok && $has_attachment) {
11980
            $courseDir = api_get_course_path().'/upload/learning_path/images';
11981
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
11982
            $updir = $sys_course_path.$courseDir;
11983
            // Try to add an extension to the file if it hasn't one.
11984
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
11985
11986
            if (filter_extension($new_file_name)) {
11987
                $file_extension = explode('.', $image_array['name']);
11988
                $file_extension = strtolower($file_extension[count($file_extension) - 1]);
11989
                $filename = uniqid('');
11990
                $new_file_name = $filename.'.'.$file_extension;
11991
                $new_path = $updir.'/'.$new_file_name;
11992
11993
                // Resize the image.
11994
                $temp = new Image($image_array['tmp_name']);
11995
                $temp->resize(104);
11996
                $result = $temp->send_image($new_path);
11997
11998
                // Storing the image filename.
11999
                if ($result) {
12000
                    $this->set_preview_image($new_file_name);
12001
12002
                    //Resize to 64px to use on course homepage
12003
                    $temp->resize(64);
12004
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
12005
12006
                    return true;
12007
                }
12008
            }
12009
        }
12010
12011
        return false;
12012
    }
12013
12014
    /**
12015
     * @param int    $lp_id
12016
     * @param string $status
12017
     */
12018
    public function set_autolaunch($lp_id, $status)
12019
    {
12020
        $course_id = api_get_course_int_id();
12021
        $lp_id = (int) $lp_id;
12022
        $status = (int) $status;
12023
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
12024
12025
        // Setting everything to autolaunch = 0
12026
        $attributes['autolaunch'] = 0;
12027
        $where = [
12028
            'session_id = ? AND c_id = ? ' => [
12029
                api_get_session_id(),
12030
                $course_id,
12031
            ],
12032
        ];
12033
        Database::update($lp_table, $attributes, $where);
12034
        if ($status == 1) {
12035
            //Setting my lp_id to autolaunch = 1
12036
            $attributes['autolaunch'] = 1;
12037
            $where = [
12038
                'iid = ? AND session_id = ? AND c_id = ?' => [
12039
                    $lp_id,
12040
                    api_get_session_id(),
12041
                    $course_id,
12042
                ],
12043
            ];
12044
            Database::update($lp_table, $attributes, $where);
12045
        }
12046
    }
12047
12048
    /**
12049
     * Gets previous_item_id for the next element of the lp_item table.
12050
     *
12051
     * @author Isaac flores paz
12052
     *
12053
     * @return int Previous item ID
12054
     */
12055
    public function select_previous_item_id()
12056
    {
12057
        $course_id = api_get_course_int_id();
12058
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12059
12060
        // Get the max order of the items
12061
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
12062
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
12063
        $rs_max_order = Database::query($sql);
12064
        $row_max_order = Database::fetch_object($rs_max_order);
12065
        $max_order = $row_max_order->display_order;
12066
        // Get the previous item ID
12067
        $sql = "SELECT iid as previous FROM $table_lp_item
12068
                WHERE
12069
                    c_id = $course_id AND
12070
                    lp_id = ".$this->lp_id." AND
12071
                    display_order = '$max_order' ";
12072
        $rs_max = Database::query($sql);
12073
        $row_max = Database::fetch_object($rs_max);
12074
12075
        // Return the previous item ID
12076
        return $row_max->previous;
12077
    }
12078
12079
    /**
12080
     * Copies an LP.
12081
     */
12082
    public function copy()
12083
    {
12084
        // Course builder
12085
        $cb = new CourseBuilder();
12086
12087
        //Setting tools that will be copied
12088
        $cb->set_tools_to_build(['learnpaths']);
12089
12090
        //Setting elements that will be copied
12091
        $cb->set_tools_specific_id_list(
12092
            ['learnpaths' => [$this->lp_id]]
12093
        );
12094
12095
        $course = $cb->build();
12096
12097
        //Course restorer
12098
        $course_restorer = new CourseRestorer($course);
12099
        $course_restorer->set_add_text_in_items(true);
12100
        $course_restorer->set_tool_copy_settings(
12101
            ['learnpaths' => ['reset_dates' => true]]
12102
        );
12103
        $course_restorer->restore(
12104
            api_get_course_id(),
12105
            api_get_session_id(),
12106
            false,
12107
            false
12108
        );
12109
    }
12110
12111
    /**
12112
     * Verify document size.
12113
     *
12114
     * @param string $s
12115
     *
12116
     * @return bool
12117
     */
12118
    public static function verify_document_size($s)
12119
    {
12120
        $post_max = ini_get('post_max_size');
12121
        if (substr($post_max, -1, 1) == 'M') {
12122
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
12123
        } elseif (substr($post_max, -1, 1) == 'G') {
12124
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
12125
        }
12126
        $upl_max = ini_get('upload_max_filesize');
12127
        if (substr($upl_max, -1, 1) == 'M') {
12128
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
12129
        } elseif (substr($upl_max, -1, 1) == 'G') {
12130
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
12131
        }
12132
        $documents_total_space = DocumentManager::documents_total_space();
12133
        $course_max_space = DocumentManager::get_course_quota();
12134
        $total_size = filesize($s) + $documents_total_space;
12135
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
12136
            return true;
12137
        }
12138
12139
        return false;
12140
    }
12141
12142
    /**
12143
     * Clear LP prerequisites.
12144
     */
12145
    public function clear_prerequisites()
12146
    {
12147
        $course_id = $this->get_course_int_id();
12148
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12149
        $lp_id = $this->get_id();
12150
        // Cleaning prerequisites
12151
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
12152
                WHERE c_id = $course_id AND lp_id = $lp_id";
12153
        Database::query($sql);
12154
12155
        // Cleaning mastery score for exercises
12156
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
12157
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
12158
        Database::query($sql);
12159
    }
12160
12161
    public function set_previous_step_as_prerequisite_for_all_items()
12162
    {
12163
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12164
        $course_id = $this->get_course_int_id();
12165
        $lp_id = $this->get_id();
12166
12167
        if (!empty($this->items)) {
12168
            $previous_item_id = null;
12169
            $previous_item_max = 0;
12170
            $previous_item_type = null;
12171
            $last_item_not_dir = null;
12172
            $last_item_not_dir_type = null;
12173
            $last_item_not_dir_max = null;
12174
12175
            foreach ($this->ordered_items as $itemId) {
12176
                $item = $this->getItem($itemId);
12177
                // if there was a previous item... (otherwise jump to set it)
12178
                if (!empty($previous_item_id)) {
12179
                    $current_item_id = $item->get_id(); //save current id
12180
                    if ($item->get_type() != 'dir') {
12181
                        // Current item is not a folder, so it qualifies to get a prerequisites
12182
                        if ($last_item_not_dir_type == 'quiz') {
12183
                            // if previous is quiz, mark its max score as default score to be achieved
12184
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
12185
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
12186
                            Database::query($sql);
12187
                        }
12188
                        // now simply update the prerequisite to set it to the last non-chapter item
12189
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
12190
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
12191
                        Database::query($sql);
12192
                        // record item as 'non-chapter' reference
12193
                        $last_item_not_dir = $item->get_id();
12194
                        $last_item_not_dir_type = $item->get_type();
12195
                        $last_item_not_dir_max = $item->get_max();
12196
                    }
12197
                } else {
12198
                    if ($item->get_type() != 'dir') {
12199
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
12200
                        $last_item_not_dir = $item->get_id();
12201
                        $last_item_not_dir_type = $item->get_type();
12202
                        $last_item_not_dir_max = $item->get_max();
12203
                    }
12204
                }
12205
                // Saving the item as "previous item" for the next loop
12206
                $previous_item_id = $item->get_id();
12207
                $previous_item_max = $item->get_max();
12208
                $previous_item_type = $item->get_type();
12209
            }
12210
        }
12211
    }
12212
12213
    /**
12214
     * @param array $params
12215
     *
12216
     * @throws \Doctrine\ORM\OptimisticLockException
12217
     *
12218
     * @return int
12219
     */
12220
    public static function createCategory($params)
12221
    {
12222
        $em = Database::getManager();
12223
        $item = new CLpCategory();
12224
        $item->setName($params['name']);
12225
        $item->setCId($params['c_id']);
12226
        $em->persist($item);
12227
        $em->flush();
12228
12229
        $id = $item->getId();
12230
12231
        $sessionId = api_get_session_id();
12232
        if (!empty($sessionId) && api_get_configuration_value('allow_session_lp_category')) {
12233
            $table = Database::get_course_table(TABLE_LP_CATEGORY);
12234
            $sql = "UPDATE $table SET session_id = $sessionId WHERE iid = $id";
12235
            Database::query($sql);
12236
        }
12237
12238
        api_item_property_update(
12239
            api_get_course_info(),
12240
            TOOL_LEARNPATH_CATEGORY,
12241
            $id,
12242
            'visible',
12243
            api_get_user_id()
12244
        );
12245
12246
        return $item->getId();
12247
    }
12248
12249
    /**
12250
     * @param array $params
12251
     */
12252
    public static function updateCategory($params)
12253
    {
12254
        $em = Database::getManager();
12255
        $item = self::getCategory($params['id']);
12256
12257
        if ($item) {
12258
            $item->setName($params['name']);
12259
            $em->merge($item);
12260
            $em->flush();
12261
        }
12262
    }
12263
12264
    /**
12265
     * @param int $id
12266
     */
12267
    public static function moveUpCategory($id)
12268
    {
12269
        $item = self::getCategory($id);
12270
        if ($item) {
12271
            $em = Database::getManager();
12272
            $position = $item->getPosition() - 1;
12273
            $item->setPosition($position);
12274
            $em->persist($item);
12275
            $em->flush();
12276
        }
12277
    }
12278
12279
    /**
12280
     * @param int $id
12281
     *
12282
     * @throws \Doctrine\ORM\ORMException
12283
     * @throws \Doctrine\ORM\OptimisticLockException
12284
     * @throws \Doctrine\ORM\TransactionRequiredException
12285
     */
12286
    public static function moveDownCategory($id)
12287
    {
12288
        $item = self::getCategory($id);
12289
        if ($item) {
12290
            $em = Database::getManager();
12291
            $position = $item->getPosition() + 1;
12292
            $item->setPosition($position);
12293
            $em->persist($item);
12294
            $em->flush();
12295
        }
12296
    }
12297
12298
    public static function getLpList($courseId, $sessionId, $onlyActiveLp = true)
12299
    {
12300
        $TABLE_LP = Database::get_course_table(TABLE_LP_MAIN);
12301
        $TABLE_ITEM_PROPERTY = Database::get_course_table(TABLE_ITEM_PROPERTY);
12302
        $courseId = (int) $courseId;
12303
        $sessionId = (int) $sessionId;
12304
12305
        $sql = "SELECT lp.id, lp.name
12306
                FROM $TABLE_LP lp
12307
                INNER JOIN $TABLE_ITEM_PROPERTY ip
12308
                ON lp.id = ip.ref
12309
                WHERE lp.c_id = $courseId ";
12310
12311
        if (!empty($sessionId)) {
12312
            $sql .= "AND ip.session_id = $sessionId ";
12313
        }
12314
12315
        if ($onlyActiveLp) {
12316
            $sql .= "AND ip.tool = 'learnpath' ";
12317
            $sql .= "AND ip.visibility = 1 ";
12318
        }
12319
12320
        $sql .= "GROUP BY lp.id";
12321
12322
        $result = Database::query($sql);
12323
12324
        return Database::store_result($result, 'ASSOC');
12325
    }
12326
12327
    /**
12328
     * @param int $courseId
12329
     *
12330
     * @throws \Doctrine\ORM\Query\QueryException
12331
     *
12332
     * @return int|mixed
12333
     */
12334
    public static function getCountCategories($courseId)
12335
    {
12336
        if (empty($courseId)) {
12337
            return 0;
12338
        }
12339
        $em = Database::getManager();
12340
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
12341
        $query->setParameter('id', $courseId);
12342
12343
        return $query->getSingleScalarResult();
12344
    }
12345
12346
    /**
12347
     * @param int $courseId
12348
     *
12349
     * @return CLpCategory[]
12350
     */
12351
    public static function getCategories($courseId)
12352
    {
12353
        $em = Database::getManager();
12354
12355
        // Using doctrine extensions
12356
        /** @var SortableRepository $repo */
12357
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
12358
12359
        return $repo->getBySortableGroupsQuery(['cId' => $courseId])->getResult();
12360
    }
12361
12362
    public static function getCategorySessionId($id)
12363
    {
12364
        if (false === api_get_configuration_value('allow_session_lp_category')) {
12365
            return 0;
12366
        }
12367
12368
        $table = Database::get_course_table(TABLE_LP_CATEGORY);
12369
        $id = (int) $id;
12370
12371
        $sql = "SELECT session_id FROM $table WHERE iid = $id";
12372
        $result = Database::query($sql);
12373
        $result = Database::fetch_array($result, 'ASSOC');
12374
12375
        if ($result) {
12376
            return (int) $result['session_id'];
12377
        }
12378
12379
        return 0;
12380
    }
12381
12382
    /**
12383
     * @param int $id
12384
     *
12385
     * @return CLpCategory
12386
     */
12387
    public static function getCategory($id)
12388
    {
12389
        $id = (int) $id;
12390
        $em = Database::getManager();
12391
12392
        return $em->find('ChamiloCourseBundle:CLpCategory', $id);
12393
    }
12394
12395
    /**
12396
     * @param int $courseId
12397
     *
12398
     * @return CLpCategory[]
12399
     */
12400
    public static function getCategoryByCourse($courseId)
12401
    {
12402
        $em = Database::getManager();
12403
12404
        return $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(['cId' => $courseId]);
12405
    }
12406
12407
    /**
12408
     * @param int $id
12409
     *
12410
     * @return bool
12411
     */
12412
    public static function deleteCategory($id)
12413
    {
12414
        $em = Database::getManager();
12415
        $id = (int) $id;
12416
        $item = self::getCategory($id);
12417
        if ($item) {
12418
            $courseId = $item->getCId();
12419
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
12420
            $query->setParameter('id', $courseId);
12421
            $query->setParameter('catId', $item->getId());
12422
            $lps = $query->getResult();
12423
12424
            // Setting category = 0.
12425
            if ($lps) {
12426
                foreach ($lps as $lpItem) {
12427
                    $lpItem->setCategoryId(0);
12428
                }
12429
            }
12430
12431
            if (api_get_configuration_value('allow_lp_subscription_to_usergroups')) {
12432
                $table = Database::get_course_table(TABLE_LP_CATEGORY_REL_USERGROUP);
12433
                $sql = "DELETE FROM $table
12434
                        WHERE
12435
                            lp_category_id = $id AND
12436
                            c_id = $courseId ";
12437
                Database::query($sql);
12438
            }
12439
12440
            // Removing category.
12441
            $em->remove($item);
12442
            $em->flush();
12443
12444
            $courseInfo = api_get_course_info_by_id($courseId);
12445
            $sessionId = api_get_session_id();
12446
12447
            // Delete link tool
12448
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
12449
            $link = 'lp/lp_controller.php?cidReq='.$courseInfo['code'].'&id_session='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
12450
            // Delete tools
12451
            $sql = "DELETE FROM $tbl_tool
12452
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
12453
            Database::query($sql);
12454
12455
            return true;
12456
        }
12457
12458
        return false;
12459
    }
12460
12461
    /**
12462
     * @param int  $courseId
12463
     * @param bool $addSelectOption
12464
     *
12465
     * @return mixed
12466
     */
12467
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
12468
    {
12469
        $items = self::getCategoryByCourse($courseId);
12470
        $cats = [];
12471
        if ($addSelectOption) {
12472
            $cats = [get_lang('SelectACategory')];
12473
        }
12474
12475
        $checkSession = false;
12476
        $sessionId = api_get_session_id();
12477
        if (api_get_configuration_value('allow_session_lp_category')) {
12478
            $checkSession = true;
12479
        }
12480
12481
        if (!empty($items)) {
12482
            foreach ($items as $cat) {
12483
                $categoryId = $cat->getId();
12484
                if ($checkSession) {
12485
                    $inSession = self::getCategorySessionId($categoryId);
12486
                    if ($inSession != $sessionId) {
12487
                        continue;
12488
                    }
12489
                }
12490
                $cats[$categoryId] = $cat->getName();
12491
            }
12492
        }
12493
12494
        return $cats;
12495
    }
12496
12497
    /**
12498
     * @param string $courseCode
12499
     * @param int    $lpId
12500
     * @param int    $user_id
12501
     *
12502
     * @return learnpath
12503
     */
12504
    public static function getLpFromSession($courseCode, $lpId, $user_id)
12505
    {
12506
        $debug = 0;
12507
        $learnPath = null;
12508
        $lpObject = Session::read('lpobject');
12509
        if ($lpObject !== null) {
12510
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
12511
            if ($debug) {
12512
                error_log('getLpFromSession: unserialize');
12513
                error_log('------getLpFromSession------');
12514
                error_log('------unserialize------');
12515
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12516
                error_log("api_get_sessionid: ".api_get_session_id());
12517
            }
12518
        }
12519
12520
        if (!is_object($learnPath)) {
12521
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
12522
            if ($debug) {
12523
                error_log('------getLpFromSession------');
12524
                error_log('getLpFromSession: create new learnpath');
12525
                error_log("create new LP with $courseCode - $lpId - $user_id");
12526
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12527
                error_log("api_get_sessionid: ".api_get_session_id());
12528
            }
12529
        }
12530
12531
        return $learnPath;
12532
    }
12533
12534
    /**
12535
     * @param int $itemId
12536
     *
12537
     * @return learnpathItem|false
12538
     */
12539
    public function getItem($itemId)
12540
    {
12541
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
12542
            return $this->items[$itemId];
12543
        }
12544
12545
        return false;
12546
    }
12547
12548
    /**
12549
     * @return int
12550
     */
12551
    public function getCurrentAttempt()
12552
    {
12553
        $attempt = $this->getItem($this->get_current_item_id());
12554
        if ($attempt) {
12555
            $attemptId = $attempt->get_attempt_id();
12556
12557
            return $attemptId;
12558
        }
12559
12560
        return 0;
12561
    }
12562
12563
    /**
12564
     * @return int
12565
     */
12566
    public function getCategoryId()
12567
    {
12568
        return (int) $this->categoryId;
12569
    }
12570
12571
    /**
12572
     * @param int $categoryId
12573
     *
12574
     * @return bool
12575
     */
12576
    public function setCategoryId($categoryId)
12577
    {
12578
        $this->categoryId = (int) $categoryId;
12579
        $table = Database::get_course_table(TABLE_LP_MAIN);
12580
        $lp_id = $this->get_id();
12581
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
12582
                WHERE iid = $lp_id";
12583
        Database::query($sql);
12584
12585
        return true;
12586
    }
12587
12588
    /**
12589
     * Get whether this is a learning path with the possibility to subscribe
12590
     * users or not.
12591
     *
12592
     * @return int
12593
     */
12594
    public function getSubscribeUsers()
12595
    {
12596
        return $this->subscribeUsers;
12597
    }
12598
12599
    /**
12600
     * Set whether this is a learning path with the possibility to subscribe
12601
     * users or not.
12602
     *
12603
     * @param int $value (0 = false, 1 = true)
12604
     *
12605
     * @return bool
12606
     */
12607
    public function setSubscribeUsers($value)
12608
    {
12609
        $this->subscribeUsers = (int) $value;
12610
        $table = Database::get_course_table(TABLE_LP_MAIN);
12611
        $lp_id = $this->get_id();
12612
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
12613
                WHERE iid = $lp_id";
12614
        Database::query($sql);
12615
12616
        return true;
12617
    }
12618
12619
    /**
12620
     * Calculate the count of stars for a user in this LP
12621
     * This calculation is based on the following rules:
12622
     * - the student gets one star when he gets to 50% of the learning path
12623
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
12624
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
12625
     * - the student gets the final star when the score for the *last* test is >= 80%.
12626
     *
12627
     * @param int $sessionId Optional. The session ID
12628
     *
12629
     * @return int The count of stars
12630
     */
12631
    public function getCalculateStars($sessionId = 0)
12632
    {
12633
        $stars = 0;
12634
        $progress = self::getProgress(
12635
            $this->lp_id,
12636
            $this->user_id,
12637
            $this->course_int_id,
12638
            $sessionId
12639
        );
12640
12641
        if ($progress >= 50) {
12642
            $stars++;
12643
        }
12644
12645
        // Calculate stars chapters evaluation
12646
        $exercisesItems = $this->getExercisesItems();
12647
12648
        if (!empty($exercisesItems)) {
12649
            $totalResult = 0;
12650
12651
            foreach ($exercisesItems as $exerciseItem) {
12652
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12653
                    $this->user_id,
12654
                    $exerciseItem->path,
12655
                    $this->course_int_id,
12656
                    $sessionId,
12657
                    $this->lp_id,
12658
                    $exerciseItem->db_id
12659
                );
12660
12661
                $exerciseResultInfo = end($exerciseResultInfo);
12662
12663
                if (!$exerciseResultInfo) {
12664
                    continue;
12665
                }
12666
12667
                if (!empty($exerciseResultInfo['exe_weighting'])) {
12668
                    $exerciseResult = $exerciseResultInfo['exe_result'] * 100 / $exerciseResultInfo['exe_weighting'];
12669
                } else {
12670
                    $exerciseResult = 0;
12671
                }
12672
                $totalResult += $exerciseResult;
12673
            }
12674
12675
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
12676
12677
            if ($totalExerciseAverage >= 50) {
12678
                $stars++;
12679
            }
12680
12681
            if ($totalExerciseAverage >= 80) {
12682
                $stars++;
12683
            }
12684
        }
12685
12686
        // Calculate star for final evaluation
12687
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12688
12689
        if (!empty($finalEvaluationItem)) {
12690
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12691
                $this->user_id,
12692
                $finalEvaluationItem->path,
12693
                $this->course_int_id,
12694
                $sessionId,
12695
                $this->lp_id,
12696
                $finalEvaluationItem->db_id
12697
            );
12698
12699
            $evaluationResultInfo = end($evaluationResultInfo);
12700
12701
            if ($evaluationResultInfo) {
12702
                $evaluationResult = $evaluationResultInfo['exe_result'] * 100 / $evaluationResultInfo['exe_weighting'];
12703
12704
                if ($evaluationResult >= 80) {
12705
                    $stars++;
12706
                }
12707
            }
12708
        }
12709
12710
        return $stars;
12711
    }
12712
12713
    /**
12714
     * Get the items of exercise type.
12715
     *
12716
     * @return array The items. Otherwise return false
12717
     */
12718
    public function getExercisesItems()
12719
    {
12720
        $exercises = [];
12721
        foreach ($this->items as $item) {
12722
            if ($item->type != 'quiz') {
12723
                continue;
12724
            }
12725
            $exercises[] = $item;
12726
        }
12727
12728
        array_pop($exercises);
12729
12730
        return $exercises;
12731
    }
12732
12733
    /**
12734
     * Get the item of exercise type (evaluation type).
12735
     *
12736
     * @return array The final evaluation. Otherwise return false
12737
     */
12738
    public function getFinalEvaluationItem()
12739
    {
12740
        $exercises = [];
12741
        foreach ($this->items as $item) {
12742
            if ($item->type != 'quiz') {
12743
                continue;
12744
            }
12745
12746
            $exercises[] = $item;
12747
        }
12748
12749
        return array_pop($exercises);
12750
    }
12751
12752
    /**
12753
     * Calculate the total points achieved for the current user in this learning path.
12754
     *
12755
     * @param int $sessionId Optional. The session Id
12756
     *
12757
     * @return int
12758
     */
12759
    public function getCalculateScore($sessionId = 0)
12760
    {
12761
        // Calculate stars chapters evaluation
12762
        $exercisesItems = $this->getExercisesItems();
12763
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12764
        $totalExercisesResult = 0;
12765
        $totalEvaluationResult = 0;
12766
12767
        if ($exercisesItems !== false) {
12768
            foreach ($exercisesItems as $exerciseItem) {
12769
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12770
                    $this->user_id,
12771
                    $exerciseItem->path,
12772
                    $this->course_int_id,
12773
                    $sessionId,
12774
                    $this->lp_id,
12775
                    $exerciseItem->db_id
12776
                );
12777
12778
                $exerciseResultInfo = end($exerciseResultInfo);
12779
12780
                if (!$exerciseResultInfo) {
12781
                    continue;
12782
                }
12783
12784
                $totalExercisesResult += $exerciseResultInfo['exe_result'];
12785
            }
12786
        }
12787
12788
        if (!empty($finalEvaluationItem)) {
12789
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12790
                $this->user_id,
12791
                $finalEvaluationItem->path,
12792
                $this->course_int_id,
12793
                $sessionId,
12794
                $this->lp_id,
12795
                $finalEvaluationItem->db_id
12796
            );
12797
12798
            $evaluationResultInfo = end($evaluationResultInfo);
12799
12800
            if ($evaluationResultInfo) {
12801
                $totalEvaluationResult += $evaluationResultInfo['exe_result'];
12802
            }
12803
        }
12804
12805
        return $totalExercisesResult + $totalEvaluationResult;
12806
    }
12807
12808
    /**
12809
     * Check if URL is not allowed to be show in a iframe.
12810
     *
12811
     * @param string $src
12812
     *
12813
     * @return string
12814
     */
12815
    public function fixBlockedLinks($src)
12816
    {
12817
        $urlInfo = parse_url($src);
12818
12819
        $platformProtocol = 'https';
12820
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
12821
            $platformProtocol = 'http';
12822
        }
12823
12824
        $protocolFixApplied = false;
12825
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
12826
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
12827
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
12828
12829
        if ($platformProtocol != $scheme) {
12830
            Session::write('x_frame_source', $src);
12831
            $src = 'blank.php?error=x_frames_options';
12832
            $protocolFixApplied = true;
12833
        }
12834
12835
        if ($protocolFixApplied == false) {
12836
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
12837
                // Check X-Frame-Options
12838
                $ch = curl_init();
12839
                $options = [
12840
                    CURLOPT_URL => $src,
12841
                    CURLOPT_RETURNTRANSFER => true,
12842
                    CURLOPT_HEADER => true,
12843
                    CURLOPT_FOLLOWLOCATION => true,
12844
                    CURLOPT_ENCODING => "",
12845
                    CURLOPT_AUTOREFERER => true,
12846
                    CURLOPT_CONNECTTIMEOUT => 120,
12847
                    CURLOPT_TIMEOUT => 120,
12848
                    CURLOPT_MAXREDIRS => 10,
12849
                ];
12850
12851
                $proxySettings = api_get_configuration_value('proxy_settings');
12852
                if (!empty($proxySettings) &&
12853
                    isset($proxySettings['curl_setopt_array'])
12854
                ) {
12855
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
12856
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
12857
                }
12858
12859
                curl_setopt_array($ch, $options);
12860
                $response = curl_exec($ch);
12861
                $httpCode = curl_getinfo($ch);
12862
                $headers = substr($response, 0, $httpCode['header_size']);
12863
12864
                $error = false;
12865
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
12866
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
12867
                ) {
12868
                    $error = true;
12869
                }
12870
12871
                if ($error) {
12872
                    Session::write('x_frame_source', $src);
12873
                    $src = 'blank.php?error=x_frames_options';
12874
                }
12875
            }
12876
        }
12877
12878
        return $src;
12879
    }
12880
12881
    /**
12882
     * Check if this LP has a created forum in the basis course.
12883
     *
12884
     * @return bool
12885
     */
12886
    public function lpHasForum()
12887
    {
12888
        $forumTable = Database::get_course_table(TABLE_FORUM);
12889
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12890
12891
        $fakeFrom = "
12892
            $forumTable f
12893
            INNER JOIN $itemProperty ip
12894
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
12895
        ";
12896
12897
        $resultData = Database::select(
12898
            'COUNT(f.iid) AS qty',
12899
            $fakeFrom,
12900
            [
12901
                'where' => [
12902
                    'ip.visibility != ? AND ' => 2,
12903
                    'ip.tool = ? AND ' => TOOL_FORUM,
12904
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12905
                    'f.lp_id = ?' => intval($this->lp_id),
12906
                ],
12907
            ],
12908
            'first'
12909
        );
12910
12911
        return $resultData['qty'] > 0;
12912
    }
12913
12914
    /**
12915
     * Get the forum for this learning path.
12916
     *
12917
     * @param int $sessionId
12918
     *
12919
     * @return bool
12920
     */
12921
    public function getForum($sessionId = 0)
12922
    {
12923
        $forumTable = Database::get_course_table(TABLE_FORUM);
12924
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12925
12926
        $fakeFrom = "$forumTable f
12927
            INNER JOIN $itemProperty ip ";
12928
12929
        if ($this->lp_session_id == 0) {
12930
            $fakeFrom .= "
12931
                ON (
12932
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND (
12933
                        f.session_id = ip.session_id OR ip.session_id IS NULL
12934
                    )
12935
                )
12936
            ";
12937
        } else {
12938
            $fakeFrom .= "
12939
                ON (
12940
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND f.session_id = ip.session_id
12941
                )
12942
            ";
12943
        }
12944
12945
        $resultData = Database::select(
12946
            'f.*',
12947
            $fakeFrom,
12948
            [
12949
                'where' => [
12950
                    'ip.visibility != ? AND ' => 2,
12951
                    'ip.tool = ? AND ' => TOOL_FORUM,
12952
                    'f.session_id = ? AND ' => $sessionId,
12953
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12954
                    'f.lp_id = ?' => intval($this->lp_id),
12955
                ],
12956
            ],
12957
            'first'
12958
        );
12959
12960
        if (empty($resultData)) {
12961
            return false;
12962
        }
12963
12964
        return $resultData;
12965
    }
12966
12967
    /**
12968
     * Create a forum for this learning path.
12969
     *
12970
     * @param int $forumCategoryId
12971
     *
12972
     * @return int The forum ID if was created. Otherwise return false
12973
     */
12974
    public function createForum($forumCategoryId)
12975
    {
12976
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
12977
12978
        $forumId = store_forum(
12979
            [
12980
                'lp_id' => $this->lp_id,
12981
                'forum_title' => $this->name,
12982
                'forum_comment' => null,
12983
                'forum_category' => (int) $forumCategoryId,
12984
                'students_can_edit_group' => ['students_can_edit' => 0],
12985
                'allow_new_threads_group' => ['allow_new_threads' => 0],
12986
                'default_view_type_group' => ['default_view_type' => 'flat'],
12987
                'group_forum' => 0,
12988
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
12989
            ],
12990
            [],
12991
            true
12992
        );
12993
12994
        return $forumId;
12995
    }
12996
12997
    /**
12998
     * Get the LP Final Item form.
12999
     *
13000
     * @throws Exception
13001
     * @throws HTML_QuickForm_Error
13002
     *
13003
     * @return string
13004
     */
13005
    public function getFinalItemForm()
13006
    {
13007
        $finalItem = $this->getFinalItem();
13008
        $title = '';
13009
13010
        if ($finalItem) {
13011
            $title = $finalItem->get_title();
13012
            $buttonText = get_lang('Save');
13013
            $content = $this->getSavedFinalItem();
13014
        } else {
13015
            $buttonText = get_lang('LPCreateDocument');
13016
            $content = $this->getFinalItemTemplate();
13017
        }
13018
13019
        $courseInfo = api_get_course_info();
13020
        $result = $this->generate_lp_folder($courseInfo);
13021
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
13022
        $relative_prefix = '../../';
13023
13024
        $editorConfig = [
13025
            'ToolbarSet' => 'LearningPathDocuments',
13026
            'Width' => '100%',
13027
            'Height' => '500',
13028
            'FullPage' => true,
13029
            'CreateDocumentDir' => $relative_prefix,
13030
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
13031
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
13032
        ];
13033
13034
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
13035
            'type' => 'document',
13036
            'lp_id' => $this->lp_id,
13037
        ]);
13038
13039
        $form = new FormValidator('final_item', 'POST', $url);
13040
        $form->addText('title', get_lang('Title'));
13041
        $form->addButtonSave($buttonText);
13042
        $form->addHtml(
13043
            Display::return_message(
13044
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
13045
                'normal',
13046
                false
13047
            )
13048
        );
13049
13050
        $renderer = $form->defaultRenderer();
13051
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
13052
13053
        $form->addHtmlEditor(
13054
            'content_lp_certificate',
13055
            null,
13056
            true,
13057
            false,
13058
            $editorConfig,
13059
            true
13060
        );
13061
        $form->addHidden('action', 'add_final_item');
13062
        $form->addHidden('path', Session::read('pathItem'));
13063
        $form->addHidden('previous', $this->get_last());
13064
        $form->setDefaults(
13065
            ['title' => $title, 'content_lp_certificate' => $content]
13066
        );
13067
13068
        if ($form->validate()) {
13069
            $values = $form->exportValues();
13070
            $lastItemId = $this->getLastInFirstLevel();
13071
13072
            if (!$finalItem) {
13073
                $documentId = $this->create_document(
13074
                    $this->course_info,
13075
                    $values['content_lp_certificate'],
13076
                    $values['title']
13077
                );
13078
                $this->add_item(
13079
                    0,
13080
                    $lastItemId,
13081
                    'final_item',
13082
                    $documentId,
13083
                    $values['title'],
13084
                    ''
13085
                );
13086
13087
                Display::addFlash(
13088
                    Display::return_message(get_lang('Added'))
13089
                );
13090
            } else {
13091
                $this->edit_document($this->course_info);
13092
            }
13093
        }
13094
13095
        return $form->returnForm();
13096
    }
13097
13098
    /**
13099
     * Check if the current lp item is first, both, last or none from lp list.
13100
     *
13101
     * @param int $currentItemId
13102
     *
13103
     * @return string
13104
     */
13105
    public function isFirstOrLastItem($currentItemId)
13106
    {
13107
        $lpItemId = [];
13108
        $typeListNotToVerify = self::getChapterTypes();
13109
13110
        // Using get_toc() function instead $this->items because returns the correct order of the items
13111
        foreach ($this->get_toc() as $item) {
13112
            if (!in_array($item['type'], $typeListNotToVerify)) {
13113
                $lpItemId[] = $item['id'];
13114
            }
13115
        }
13116
13117
        $lastLpItemIndex = count($lpItemId) - 1;
13118
        $position = array_search($currentItemId, $lpItemId);
13119
13120
        switch ($position) {
13121
            case 0:
13122
                if (!$lastLpItemIndex) {
13123
                    $answer = 'both';
13124
                    break;
13125
                }
13126
13127
                $answer = 'first';
13128
                break;
13129
            case $lastLpItemIndex:
13130
                $answer = 'last';
13131
                break;
13132
            default:
13133
                $answer = 'none';
13134
        }
13135
13136
        return $answer;
13137
    }
13138
13139
    /**
13140
     * Get whether this is a learning path with the accumulated SCORM time or not.
13141
     *
13142
     * @return int
13143
     */
13144
    public function getAccumulateScormTime()
13145
    {
13146
        return $this->accumulateScormTime;
13147
    }
13148
13149
    /**
13150
     * Set whether this is a learning path with the accumulated SCORM time or not.
13151
     *
13152
     * @param int $value (0 = false, 1 = true)
13153
     *
13154
     * @return bool Always returns true
13155
     */
13156
    public function setAccumulateScormTime($value)
13157
    {
13158
        $this->accumulateScormTime = (int) $value;
13159
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
13160
        $lp_id = $this->get_id();
13161
        $sql = "UPDATE $lp_table
13162
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
13163
                WHERE iid = $lp_id";
13164
        Database::query($sql);
13165
13166
        return true;
13167
    }
13168
13169
    /**
13170
     * Returns an HTML-formatted link to a resource, to incorporate directly into
13171
     * the new learning path tool.
13172
     *
13173
     * The function is a big switch on tool type.
13174
     * In each case, we query the corresponding table for information and build the link
13175
     * with that information.
13176
     *
13177
     * @author Yannick Warnier <[email protected]> - rebranding based on
13178
     * previous work (display_addedresource_link_in_learnpath())
13179
     *
13180
     * @param int $course_id      Course code
13181
     * @param int $learningPathId The learning path ID (in lp table)
13182
     * @param int $id_in_path     the unique index in the items table
13183
     * @param int $lpViewId
13184
     * @param int $lpSessionId
13185
     *
13186
     * @return string
13187
     */
13188
    public static function rl_get_resource_link_for_learnpath(
13189
        $course_id,
13190
        $learningPathId,
13191
        $id_in_path,
13192
        $lpViewId,
13193
        $lpSessionId = 0
13194
    ) {
13195
        $session_id = api_get_session_id();
13196
        $course_info = api_get_course_info_by_id($course_id);
13197
13198
        $learningPathId = (int) $learningPathId;
13199
        $id_in_path = (int) $id_in_path;
13200
        $lpViewId = (int) $lpViewId;
13201
13202
        $em = Database::getManager();
13203
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
13204
13205
        /** @var CLpItem $rowItem */
13206
        $rowItem = $lpItemRepo->findOneBy([
13207
            'cId' => $course_id,
13208
            'lpId' => $learningPathId,
13209
            'iid' => $id_in_path,
13210
        ]);
13211
13212
        if (!$rowItem) {
13213
            // Try one more time with "id"
13214
            /** @var CLpItem $rowItem */
13215
            $rowItem = $lpItemRepo->findOneBy([
13216
                'cId' => $course_id,
13217
                'lpId' => $learningPathId,
13218
                'id' => $id_in_path,
13219
            ]);
13220
13221
            if (!$rowItem) {
13222
                return -1;
13223
            }
13224
        }
13225
13226
        $course_code = $course_info['code'];
13227
        $type = $rowItem->getItemType();
13228
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
13229
        $main_dir_path = api_get_path(WEB_CODE_PATH);
13230
        $main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
13231
        $link = '';
13232
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
13233
13234
        switch ($type) {
13235
            case 'dir':
13236
                return $main_dir_path.'lp/blank.php';
13237
            case TOOL_CALENDAR_EVENT:
13238
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
13239
            case TOOL_ANNOUNCEMENT:
13240
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
13241
            case TOOL_LINK:
13242
                $linkInfo = Link::getLinkInfo($id);
13243
                if ($linkInfo) {
13244
                    $itemPropertyInfo = api_get_item_property_info($course_id, TOOL_LINK, $id, $session_id);
13245
                    if ($itemPropertyInfo && 0 === (int) $itemPropertyInfo['visibility']) {
13246
                        return '';
13247
                    }
13248
                    if (isset($linkInfo['url'])) {
13249
                        return $linkInfo['url'];
13250
                    }
13251
                }
13252
13253
                return '';
13254
            case TOOL_QUIZ:
13255
                if (empty($id)) {
13256
                    return '';
13257
                }
13258
13259
                // Get the lp_item_view with the highest view_count.
13260
                $learnpathItemViewResult = $em
13261
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
13262
                    ->findBy(
13263
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
13264
                        ['viewCount' => 'DESC'],
13265
                        1
13266
                    );
13267
                /** @var CLpItemView $learnpathItemViewData */
13268
                $learnpathItemViewData = current($learnpathItemViewResult);
13269
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
13270
13271
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
13272
                    .http_build_query([
13273
                        'lp_init' => 1,
13274
                        'learnpath_item_view_id' => $learnpathItemViewId,
13275
                        'learnpath_id' => $learningPathId,
13276
                        'learnpath_item_id' => $id_in_path,
13277
                        'exerciseId' => $id,
13278
                    ]);
13279
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
13280
                $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
13281
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
13282
                $myrow = Database::fetch_array($result);
13283
                $path = $myrow['path'];
13284
13285
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
13286
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
13287
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;
13288
            case TOOL_FORUM:
13289
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
13290
            case TOOL_THREAD:
13291
                // forum post
13292
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
13293
                if (empty($id)) {
13294
                    return '';
13295
                }
13296
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
13297
                $result = Database::query($sql);
13298
                $myrow = Database::fetch_array($result);
13299
13300
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
13301
                    .$extraParams;
13302
            case TOOL_POST:
13303
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13304
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
13305
                $myrow = Database::fetch_array($result);
13306
13307
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
13308
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
13309
            case TOOL_READOUT_TEXT:
13310
                return api_get_path(WEB_CODE_PATH).
13311
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
13312
            case TOOL_DOCUMENT:
13313
                $repo = $em->getRepository('ChamiloCourseBundle:CDocument');
13314
                $document = $repo->findOneBy(['cId' => $course_id, 'iid' => $id]);
13315
13316
                if (empty($document)) {
13317
                    // Try with normal id
13318
                    $document = $repo->findOneBy(['cId' => $course_id, 'id' => $id]);
13319
13320
                    if (empty($document)) {
13321
                        return '';
13322
                    }
13323
                }
13324
13325
                $documentPathInfo = pathinfo($document->getPath());
13326
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v'];
13327
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
13328
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
13329
13330
                $openmethod = 2;
13331
                $officedoc = false;
13332
                Session::write('openmethod', $openmethod);
13333
                Session::write('officedoc', $officedoc);
13334
13335
                if ($showDirectUrl) {
13336
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
13337
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
13338
                        if (Link::isPdfLink($file)) {
13339
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
13340
13341
                            return $pdfUrl;
13342
                        }
13343
                    }
13344
13345
                    return $file;
13346
                }
13347
13348
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
13349
            case TOOL_LP_FINAL_ITEM:
13350
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
13351
                    .$extraParams;
13352
            case 'assignments':
13353
                return $main_dir_path.'work/work.php?'.$extraParams;
13354
            case TOOL_DROPBOX:
13355
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
13356
            case 'introduction_text': //DEPRECATED
13357
                return '';
13358
            case TOOL_COURSE_DESCRIPTION:
13359
                return $main_dir_path.'course_description?'.$extraParams;
13360
            case TOOL_GROUP:
13361
                return $main_dir_path.'group/group.php?'.$extraParams;
13362
            case TOOL_USER:
13363
                return $main_dir_path.'user/user.php?'.$extraParams;
13364
            case TOOL_STUDENTPUBLICATION:
13365
                if (!empty($rowItem->getPath())) {
13366
                    $workId = $rowItem->getPath();
13367
                    if (empty($lpSessionId) && !empty($session_id)) {
13368
                        // Check if a student publication with the same name exists in this session see BT#17700
13369
                        $title = Database::escape_string($rowItem->getTitle());
13370
                        $table = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
13371
                        $sql = "SELECT * FROM $table
13372
                                WHERE
13373
                                    active = 1 AND
13374
                                    parent_id = 0 AND
13375
                                    c_id = $course_id AND
13376
                                    session_id = $session_id AND
13377
                                    title = '$title'
13378
                                LIMIT 1";
13379
                        $result = Database::query($sql);
13380
                        if (Database::num_rows($result)) {
13381
                            $work = Database::fetch_array($result, 'ASSOC');
13382
                            if ($work) {
13383
                                $workId = $work['iid'];
13384
                            }
13385
                        }
13386
                    }
13387
13388
                    return $main_dir_path.'work/work_list.php?id='.$workId.'&'.$extraParams;
13389
                }
13390
13391
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
13392
        }
13393
13394
        return $link;
13395
    }
13396
13397
    /**
13398
     * Gets the name of a resource (generally used in learnpath when no name is provided).
13399
     *
13400
     * @author Yannick Warnier <[email protected]>
13401
     *
13402
     * @param string $course_code    Course code
13403
     * @param int    $learningPathId
13404
     * @param int    $id_in_path     The resource ID
13405
     *
13406
     * @return string
13407
     */
13408
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
13409
    {
13410
        $_course = api_get_course_info($course_code);
13411
        if (empty($_course)) {
13412
            return '';
13413
        }
13414
        $course_id = $_course['real_id'];
13415
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
13416
        $learningPathId = (int) $learningPathId;
13417
        $id_in_path = (int) $id_in_path;
13418
13419
        $sql = "SELECT item_type, title, ref
13420
                FROM $tbl_lp_item
13421
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
13422
        $res_item = Database::query($sql);
13423
13424
        if (Database::num_rows($res_item) < 1) {
13425
            return '';
13426
        }
13427
        $row_item = Database::fetch_array($res_item);
13428
        $type = strtolower($row_item['item_type']);
13429
        $id = $row_item['ref'];
13430
        $output = '';
13431
13432
        switch ($type) {
13433
            case TOOL_CALENDAR_EVENT:
13434
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
13435
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
13436
                $myrow = Database::fetch_array($result);
13437
                $output = $myrow['title'];
13438
                break;
13439
            case TOOL_ANNOUNCEMENT:
13440
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
13441
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
13442
                $myrow = Database::fetch_array($result);
13443
                $output = $myrow['title'];
13444
                break;
13445
            case TOOL_LINK:
13446
                // Doesn't take $target into account.
13447
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
13448
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
13449
                $myrow = Database::fetch_array($result);
13450
                $output = $myrow['title'];
13451
                break;
13452
            case TOOL_QUIZ:
13453
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
13454
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
13455
                $myrow = Database::fetch_array($result);
13456
                $output = $myrow['title'];
13457
                break;
13458
            case TOOL_FORUM:
13459
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
13460
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
13461
                $myrow = Database::fetch_array($result);
13462
                $output = $myrow['forum_name'];
13463
                break;
13464
            case TOOL_THREAD:
13465
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13466
                // Grabbing the title of the post.
13467
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
13468
                $result_title = Database::query($sql_title);
13469
                $myrow_title = Database::fetch_array($result_title);
13470
                $output = $myrow_title['post_title'];
13471
                break;
13472
            case TOOL_POST:
13473
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13474
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
13475
                $result = Database::query($sql);
13476
                $post = Database::fetch_array($result);
13477
                $output = $post['post_title'];
13478
                break;
13479
            case 'dir':
13480
            case TOOL_DOCUMENT:
13481
                $title = $row_item['title'];
13482
                $output = '-';
13483
                if (!empty($title)) {
13484
                    $output = $title;
13485
                }
13486
                break;
13487
            case 'hotpotatoes':
13488
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
13489
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
13490
                $myrow = Database::fetch_array($result);
13491
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
13492
                $last = count($pathname) - 1; // Making a correct name for the link.
13493
                $filename = $pathname[$last]; // Making a correct name for the link.
13494
                $myrow['path'] = rawurlencode($myrow['path']);
13495
                $output = $filename;
13496
                break;
13497
        }
13498
13499
        return stripslashes($output);
13500
    }
13501
13502
    /**
13503
     * Get the parent names for the current item.
13504
     *
13505
     * @param int $newItemId Optional. The item ID
13506
     *
13507
     * @return array
13508
     */
13509
    public function getCurrentItemParentNames($newItemId = 0)
13510
    {
13511
        $newItemId = $newItemId ?: $this->get_current_item_id();
13512
        $return = [];
13513
        $item = $this->getItem($newItemId);
13514
        $parent = $this->getItem($item->get_parent());
13515
13516
        while ($parent) {
13517
            $return[] = $parent->get_title();
13518
            $parent = $this->getItem($parent->get_parent());
13519
        }
13520
13521
        return array_reverse($return);
13522
    }
13523
13524
    /**
13525
     * Reads and process "lp_subscription_settings" setting.
13526
     *
13527
     * @return array
13528
     */
13529
    public static function getSubscriptionSettings()
13530
    {
13531
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
13532
        if (empty($subscriptionSettings)) {
13533
            // By default allow both settings
13534
            $subscriptionSettings = [
13535
                'allow_add_users_to_lp' => true,
13536
                'allow_add_users_to_lp_category' => true,
13537
            ];
13538
        } else {
13539
            $subscriptionSettings = $subscriptionSettings['options'];
13540
        }
13541
13542
        return $subscriptionSettings;
13543
    }
13544
13545
    /**
13546
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
13547
     */
13548
    public function exportToCourseBuildFormat()
13549
    {
13550
        if (!api_is_allowed_to_edit()) {
13551
            return false;
13552
        }
13553
13554
        $courseBuilder = new CourseBuilder();
13555
        $itemList = [];
13556
        /** @var learnpathItem $item */
13557
        foreach ($this->items as $item) {
13558
            $itemList[$item->get_type()][] = $item->get_path();
13559
        }
13560
13561
        if (empty($itemList)) {
13562
            return false;
13563
        }
13564
13565
        if (isset($itemList['document'])) {
13566
            // Get parents
13567
            foreach ($itemList['document'] as $documentId) {
13568
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
13569
                if (!empty($documentInfo['parents'])) {
13570
                    foreach ($documentInfo['parents'] as $parentInfo) {
13571
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
13572
                            continue;
13573
                        }
13574
                        $itemList['document'][] = $parentInfo['iid'];
13575
                    }
13576
                }
13577
            }
13578
13579
            $courseInfo = api_get_course_info();
13580
            foreach ($itemList['document'] as $documentId) {
13581
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
13582
                $items = DocumentManager::get_resources_from_source_html(
13583
                    $documentInfo['absolute_path'],
13584
                    true,
13585
                    TOOL_DOCUMENT
13586
                );
13587
13588
                if (!empty($items)) {
13589
                    foreach ($items as $item) {
13590
                        // Get information about source url
13591
                        $url = $item[0]; // url
13592
                        $scope = $item[1]; // scope (local, remote)
13593
                        $type = $item[2]; // type (rel, abs, url)
13594
13595
                        $origParseUrl = parse_url($url);
13596
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
13597
13598
                        if ($scope == 'local') {
13599
                            if ($type == 'abs' || $type == 'rel') {
13600
                                $documentFile = strstr($realOrigPath, 'document');
13601
                                if (strpos($realOrigPath, $documentFile) !== false) {
13602
                                    $documentFile = str_replace('document', '', $documentFile);
13603
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
13604
                                    // Document found! Add it to the list
13605
                                    if ($itemDocumentId) {
13606
                                        $itemList['document'][] = $itemDocumentId;
13607
                                    }
13608
                                }
13609
                            }
13610
                        }
13611
                    }
13612
                }
13613
            }
13614
13615
            $courseBuilder->build_documents(
13616
                api_get_session_id(),
13617
                $this->get_course_int_id(),
13618
                true,
13619
                $itemList['document']
13620
            );
13621
        }
13622
13623
        if (isset($itemList['quiz'])) {
13624
            $courseBuilder->build_quizzes(
13625
                api_get_session_id(),
13626
                $this->get_course_int_id(),
13627
                true,
13628
                $itemList['quiz']
13629
            );
13630
        }
13631
13632
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
13633
13634
        /*if (!empty($itemList['thread'])) {
13635
            $postList = [];
13636
            foreach ($itemList['thread'] as $postId) {
13637
                $post = get_post_information($postId);
13638
                if ($post) {
13639
                    if (!isset($itemList['forum'])) {
13640
                        $itemList['forum'] = [];
13641
                    }
13642
                    $itemList['forum'][] = $post['forum_id'];
13643
                    $postList[] = $postId;
13644
                }
13645
            }
13646
13647
            if (!empty($postList)) {
13648
                $courseBuilder->build_forum_posts(
13649
                    $this->get_course_int_id(),
13650
                    null,
13651
                    null,
13652
                    $postList
13653
                );
13654
            }
13655
        }*/
13656
13657
        if (!empty($itemList['thread'])) {
13658
            $threadList = [];
13659
            $em = Database::getManager();
13660
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
13661
            foreach ($itemList['thread'] as $threadId) {
13662
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
13663
                $thread = $repo->find($threadId);
13664
                if ($thread) {
13665
                    $itemList['forum'][] = $thread->getForumId();
13666
                    $threadList[] = $thread->getIid();
13667
                }
13668
            }
13669
13670
            if (!empty($threadList)) {
13671
                $courseBuilder->build_forum_topics(
13672
                    api_get_session_id(),
13673
                    $this->get_course_int_id(),
13674
                    null,
13675
                    $threadList
13676
                );
13677
            }
13678
        }
13679
13680
        $forumCategoryList = [];
13681
        if (isset($itemList['forum'])) {
13682
            foreach ($itemList['forum'] as $forumId) {
13683
                $forumInfo = get_forums($forumId);
13684
                $forumCategoryList[] = $forumInfo['forum_category'];
13685
            }
13686
        }
13687
13688
        if (!empty($forumCategoryList)) {
13689
            $courseBuilder->build_forum_category(
13690
                api_get_session_id(),
13691
                $this->get_course_int_id(),
13692
                true,
13693
                $forumCategoryList
13694
            );
13695
        }
13696
13697
        if (!empty($itemList['forum'])) {
13698
            $courseBuilder->build_forums(
13699
                api_get_session_id(),
13700
                $this->get_course_int_id(),
13701
                true,
13702
                $itemList['forum']
13703
            );
13704
        }
13705
13706
        if (isset($itemList['link'])) {
13707
            $courseBuilder->build_links(
13708
                api_get_session_id(),
13709
                $this->get_course_int_id(),
13710
                true,
13711
                $itemList['link']
13712
            );
13713
        }
13714
13715
        if (!empty($itemList['student_publication'])) {
13716
            $courseBuilder->build_works(
13717
                api_get_session_id(),
13718
                $this->get_course_int_id(),
13719
                true,
13720
                $itemList['student_publication']
13721
            );
13722
        }
13723
13724
        $courseBuilder->build_learnpaths(
13725
            api_get_session_id(),
13726
            $this->get_course_int_id(),
13727
            true,
13728
            [$this->get_id()],
13729
            false
13730
        );
13731
13732
        $courseBuilder->restoreDocumentsFromList();
13733
13734
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
13735
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
13736
        $result = DocumentManager::file_send_for_download(
13737
            $zipPath,
13738
            true,
13739
            $this->get_name().'.zip'
13740
        );
13741
13742
        if ($result) {
13743
            api_not_allowed();
13744
        }
13745
13746
        return true;
13747
    }
13748
13749
    /**
13750
     * Get whether this is a learning path with the accumulated work time or not.
13751
     *
13752
     * @return int
13753
     */
13754
    public function getAccumulateWorkTime()
13755
    {
13756
        return (int) $this->accumulateWorkTime;
13757
    }
13758
13759
    /**
13760
     * Get whether this is a learning path with the accumulated work time or not.
13761
     *
13762
     * @return int
13763
     */
13764
    public function getAccumulateWorkTimeTotalCourse()
13765
    {
13766
        $table = Database::get_course_table(TABLE_LP_MAIN);
13767
        $sql = "SELECT SUM(accumulate_work_time) AS total
13768
                FROM $table
13769
                WHERE c_id = ".$this->course_int_id;
13770
        $result = Database::query($sql);
13771
        $row = Database::fetch_array($result);
13772
13773
        return (int) $row['total'];
13774
    }
13775
13776
    /**
13777
     * Set whether this is a learning path with the accumulated work time or not.
13778
     *
13779
     * @param int $value (0 = false, 1 = true)
13780
     *
13781
     * @return bool
13782
     */
13783
    public function setAccumulateWorkTime($value)
13784
    {
13785
        if (!api_get_configuration_value('lp_minimum_time')) {
13786
            return false;
13787
        }
13788
13789
        $this->accumulateWorkTime = (int) $value;
13790
        $table = Database::get_course_table(TABLE_LP_MAIN);
13791
        $lp_id = $this->get_id();
13792
        $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
13793
                WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
13794
        Database::query($sql);
13795
13796
        return true;
13797
    }
13798
13799
    /**
13800
     * @param int $lpId
13801
     * @param int $courseId
13802
     *
13803
     * @return mixed
13804
     */
13805
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
13806
    {
13807
        $lpId = (int) $lpId;
13808
        $courseId = (int) $courseId;
13809
13810
        $table = Database::get_course_table(TABLE_LP_MAIN);
13811
        $sql = "SELECT accumulate_work_time
13812
                FROM $table
13813
                WHERE c_id = $courseId AND id = $lpId";
13814
        $result = Database::query($sql);
13815
        $row = Database::fetch_array($result);
13816
13817
        return $row['accumulate_work_time'];
13818
    }
13819
13820
    /**
13821
     * @param int $courseId
13822
     *
13823
     * @return int
13824
     */
13825
    public static function getAccumulateWorkTimeTotal($courseId)
13826
    {
13827
        $table = Database::get_course_table(TABLE_LP_MAIN);
13828
        $courseId = (int) $courseId;
13829
        $sql = "SELECT SUM(accumulate_work_time) AS total
13830
                FROM $table
13831
                WHERE c_id = $courseId";
13832
        $result = Database::query($sql);
13833
        $row = Database::fetch_array($result);
13834
13835
        return (int) $row['total'];
13836
    }
13837
13838
    /**
13839
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
13840
     * and put the images in.
13841
     *
13842
     * @return array
13843
     */
13844
    public static function getIconSelect()
13845
    {
13846
        $theme = api_get_visual_theme();
13847
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
13848
        $icons = ['' => get_lang('SelectAnOption')];
13849
13850
        if (is_dir($path)) {
13851
            $finder = new Finder();
13852
            $finder->files()->in($path);
13853
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
13854
            /** @var SplFileInfo $file */
13855
            foreach ($finder as $file) {
13856
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
13857
                    $icons[$file->getFilename()] = $file->getFilename();
13858
                }
13859
            }
13860
        }
13861
13862
        return $icons;
13863
    }
13864
13865
    /**
13866
     * @param int $lpId
13867
     *
13868
     * @return string
13869
     */
13870
    public static function getSelectedIcon($lpId)
13871
    {
13872
        $extraFieldValue = new ExtraFieldValue('lp');
13873
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
13874
        $icon = '';
13875
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
13876
            $icon = $lpIcon['value'];
13877
        }
13878
13879
        return $icon;
13880
    }
13881
13882
    /**
13883
     * @param int $lpId
13884
     *
13885
     * @return string
13886
     */
13887
    public static function getSelectedIconHtml($lpId)
13888
    {
13889
        $icon = self::getSelectedIcon($lpId);
13890
13891
        if (empty($icon)) {
13892
            return '';
13893
        }
13894
13895
        $theme = api_get_visual_theme();
13896
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
13897
13898
        return Display::img($path);
13899
    }
13900
13901
    /**
13902
     * @param string $value
13903
     *
13904
     * @return string
13905
     */
13906
    public function cleanItemTitle($value)
13907
    {
13908
        $value = Security::remove_XSS(strip_tags($value));
13909
13910
        return $value;
13911
    }
13912
13913
    public function setItemTitle(FormValidator $form)
13914
    {
13915
        if (api_get_configuration_value('save_titles_as_html')) {
13916
            $form->addHtmlEditor(
13917
                'title',
13918
                get_lang('Title'),
13919
                true,
13920
                false,
13921
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
13922
            );
13923
        } else {
13924
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
13925
            $form->applyFilter('title', 'trim');
13926
            $form->applyFilter('title', 'html_filter');
13927
        }
13928
    }
13929
13930
    /**
13931
     * @return array
13932
     */
13933
    public function getItemsForForm($addParentCondition = false)
13934
    {
13935
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
13936
        $course_id = api_get_course_int_id();
13937
13938
        $sql = "SELECT * FROM $tbl_lp_item
13939
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
13940
13941
        if ($addParentCondition) {
13942
            $sql .= ' AND parent_item_id = 0 ';
13943
        }
13944
        $sql .= ' ORDER BY display_order ASC';
13945
13946
        $result = Database::query($sql);
13947
        $arrLP = [];
13948
        while ($row = Database::fetch_array($result)) {
13949
            $arrLP[] = [
13950
                'iid' => $row['iid'],
13951
                'id' => $row['iid'],
13952
                'item_type' => $row['item_type'],
13953
                'title' => $this->cleanItemTitle($row['title']),
13954
                'title_raw' => $row['title'],
13955
                'path' => $row['path'],
13956
                'description' => Security::remove_XSS($row['description']),
13957
                'parent_item_id' => $row['parent_item_id'],
13958
                'previous_item_id' => $row['previous_item_id'],
13959
                'next_item_id' => $row['next_item_id'],
13960
                'display_order' => $row['display_order'],
13961
                'max_score' => $row['max_score'],
13962
                'min_score' => $row['min_score'],
13963
                'mastery_score' => $row['mastery_score'],
13964
                'prerequisite' => $row['prerequisite'],
13965
                'max_time_allowed' => $row['max_time_allowed'],
13966
                'prerequisite_min_score' => $row['prerequisite_min_score'],
13967
                'prerequisite_max_score' => $row['prerequisite_max_score'],
13968
            ];
13969
        }
13970
13971
        return $arrLP;
13972
    }
13973
13974
    /**
13975
     * Gets whether this SCORM learning path has been marked to use the score
13976
     * as progress. Takes into account whether the learnpath matches (SCORM
13977
     * content + less than 2 items).
13978
     *
13979
     * @return bool True if the score should be used as progress, false otherwise
13980
     */
13981
    public function getUseScoreAsProgress()
13982
    {
13983
        // If not a SCORM, we don't care about the setting
13984
        if ($this->get_type() != 2) {
13985
            return false;
13986
        }
13987
        // If more than one step in the SCORM, we don't care about the setting
13988
        if ($this->get_total_items_count() > 1) {
13989
            return false;
13990
        }
13991
        $extraFieldValue = new ExtraFieldValue('lp');
13992
        $doUseScore = false;
13993
        $useScore = $extraFieldValue->get_values_by_handler_and_field_variable($this->get_id(), 'use_score_as_progress');
13994
        if (!empty($useScore) && isset($useScore['value'])) {
13995
            $doUseScore = $useScore['value'];
13996
        }
13997
13998
        return $doUseScore;
13999
    }
14000
14001
    /**
14002
     * Get the user identifier (user_id or username
14003
     * Depends on scorm_api_username_as_student_id in app/config/configuration.php.
14004
     *
14005
     * @return string User ID or username, depending on configuration setting
14006
     */
14007
    public static function getUserIdentifierForExternalServices()
14008
    {
14009
        if (api_get_configuration_value('scorm_api_username_as_student_id')) {
14010
            return api_get_user_info(api_get_user_id())['username'];
14011
        } elseif (api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id') != null) {
14012
            $extraFieldValue = new ExtraFieldValue('user');
14013
            $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'));
14014
14015
            return $extrafield['value'];
14016
        } else {
14017
            return api_get_user_id();
14018
        }
14019
    }
14020
14021
    /**
14022
     * Save the new order for learning path items.
14023
     *
14024
     * We have to update parent_item_id, previous_item_id, next_item_id, display_order in the database.
14025
     *
14026
     * @param array $orderList A associative array with item ID as key and parent ID as value.
14027
     * @param int   $courseId
14028
     */
14029
    public static function sortItemByOrderList(array $orderList, $courseId = 0)
14030
    {
14031
        $courseId = $courseId ?: api_get_course_int_id();
14032
        $itemList = new LpItemOrderList();
14033
14034
        foreach ($orderList as $id => $parentId) {
14035
            $item = new LpOrderItem($id, $parentId);
14036
            $itemList->add($item);
14037
        }
14038
14039
        $parents = $itemList->getListOfParents();
14040
14041
        foreach ($parents as $parentId) {
14042
            $sameParentLpItemList = $itemList->getItemWithSameParent($parentId);
14043
            $previous_item_id = 0;
14044
            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...
14045
                $item_id = $sameParentLpItemList->list[$i]->id;
14046
                // display_order
14047
                $display_order = $i + 1;
14048
                $itemList->setParametersForId($item_id, $display_order, 'display_order');
14049
                // previous_item_id
14050
                $itemList->setParametersForId($item_id, $previous_item_id, 'previous_item_id');
14051
                $previous_item_id = $item_id;
14052
                // next_item_id
14053
                $next_item_id = 0;
14054
                if ($i < count($sameParentLpItemList->list) - 1) {
14055
                    $next_item_id = $sameParentLpItemList->list[$i + 1]->id;
14056
                }
14057
                $itemList->setParametersForId($item_id, $next_item_id, 'next_item_id');
14058
            }
14059
        }
14060
14061
        $table = Database::get_course_table(TABLE_LP_ITEM);
14062
14063
        foreach ($itemList->list as $item) {
14064
            $params = [];
14065
            $params['display_order'] = $item->display_order;
14066
            $params['previous_item_id'] = $item->previous_item_id;
14067
            $params['next_item_id'] = $item->next_item_id;
14068
            $params['parent_item_id'] = $item->parent_item_id;
14069
14070
            Database::update(
14071
                $table,
14072
                $params,
14073
                [
14074
                    'iid = ? AND c_id = ? ' => [
14075
                        (int) $item->id,
14076
                        (int) $courseId,
14077
                    ],
14078
                ]
14079
            );
14080
        }
14081
    }
14082
14083
    /**
14084
     * Get the depth level of LP item.
14085
     *
14086
     * @param array $items
14087
     * @param int   $currentItemId
14088
     *
14089
     * @return int
14090
     */
14091
    private static function get_level_for_item($items, $currentItemId)
14092
    {
14093
        $parentItemId = 0;
14094
        if (isset($items[$currentItemId])) {
14095
            $parentItemId = $items[$currentItemId]->parent;
14096
        }
14097
14098
        if ($parentItemId == 0) {
14099
            return 0;
14100
        } else {
14101
            return self::get_level_for_item($items, $parentItemId) + 1;
14102
        }
14103
    }
14104
14105
    /**
14106
     * Generate the link for a learnpath category as course tool.
14107
     *
14108
     * @param int $categoryId
14109
     *
14110
     * @return string
14111
     */
14112
    private static function getCategoryLinkForTool($categoryId)
14113
    {
14114
        $categoryId = (int) $categoryId;
14115
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
14116
            .http_build_query(
14117
                [
14118
                    'action' => 'view_category',
14119
                    'id' => $categoryId,
14120
                ]
14121
            );
14122
14123
        return $link;
14124
    }
14125
14126
    /**
14127
     * Return the scorm item type object with spaces replaced with _
14128
     * The return result is use to build a css classname like scorm_type_$return.
14129
     *
14130
     * @param $in_type
14131
     *
14132
     * @return mixed
14133
     */
14134
    private static function format_scorm_type_item($in_type)
14135
    {
14136
        return str_replace(' ', '_', $in_type);
14137
    }
14138
14139
    /**
14140
     * Check and obtain the lp final item if exist.
14141
     *
14142
     * @return learnpathItem
14143
     */
14144
    private function getFinalItem()
14145
    {
14146
        if (empty($this->items)) {
14147
            return null;
14148
        }
14149
14150
        foreach ($this->items as $item) {
14151
            if ($item->type !== 'final_item') {
14152
                continue;
14153
            }
14154
14155
            return $item;
14156
        }
14157
    }
14158
14159
    /**
14160
     * Get the LP Final Item Template.
14161
     *
14162
     * @return string
14163
     */
14164
    private function getFinalItemTemplate()
14165
    {
14166
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
14167
    }
14168
14169
    /**
14170
     * Get the LP Final Item Url.
14171
     *
14172
     * @return string
14173
     */
14174
    private function getSavedFinalItem()
14175
    {
14176
        $finalItem = $this->getFinalItem();
14177
        $doc = DocumentManager::get_document_data_by_id(
14178
            $finalItem->path,
14179
            $this->cc
14180
        );
14181
        if ($doc && file_exists($doc['absolute_path'])) {
14182
            return file_get_contents($doc['absolute_path']);
14183
        }
14184
14185
        return '';
14186
    }
14187
}
14188