Passed
Push — master ( eba669...1a8a38 )
by Julito
10:56
created

learnpath   F

Complexity

Total Complexity 1910

Size/Duplication

Total Lines 13839
Duplicated Lines 0 %

Importance

Changes 5
Bugs 2 Features 0
Metric Value
eloc 7790
dl 0
loc 13839
rs 0.8
c 5
b 2
f 0
wmc 1910

206 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 getProgressBar() 0 5 1
B update_default_view_mode() 0 40 8
A next() 0 22 5
A get_iv_objectives_array() 0 34 3
F display_edit_item() 0 143 18
F display_thread_form() 0 216 42
A get_total_items_count() 0 7 2
B save_last() 0 48 10
A get_first_item_id() 0 8 2
A get_update_queue() 0 7 2
F get_link() 0 327 60
A switch_attempt_mode() 0 21 5
F create_document() 0 160 30
D prerequisites_match() 0 67 16
A previous() 0 14 2
A tree_array() 0 7 2
B move_down() 0 54 8
A open() 0 12 2
F edit_item() 0 213 24
A set_jslib() 0 16 3
B update_default_scorm_commit() 0 32 6
C getChildrenToc() 0 61 12
A set_error_msg() 0 9 3
D stop_previous_item() 0 55 18
C start_current_item() 0 40 16
A set_publicated_on() 0 30 5
B set_current_item() 0 33 10
A get_author() 0 9 3
A set_prerequisite() 0 16 3
A set_modified_on() 0 16 3
A get_last() 0 13 3
A getHideTableOfContents() 0 3 1
A categoryIsPublished() 0 26 2
A get_type() 0 14 6
A get_maker() 0 9 3
F display_link_form() 0 191 34
F display_document_form() 0 375 73
A get_progress_bar() 0 12 1
A set_preview_image() 0 18 3
B getSiblingDirectories() 0 35 6
B get_brother_items() 0 31 6
C toggle_publish() 0 80 11
F createReadOutText() 0 121 27
A get_type_static() 0 16 3
F displayFrmReadOutText() 0 304 60
A sort_tree_array() 0 12 3
A get_items_status_list() 0 13 3
A get_theme() 0 9 3
F display_item_form() 0 231 38
F add_item() 0 215 17
B get_previous_index() 0 24 7
B set_seriousgame_mode() 0 30 6
A get_preview_image() 0 9 3
B isBlockedByPrerequisite() 0 32 8
A returnLpItemList() 0 15 2
D get_progress_bar_text() 0 43 11
A get_teacher_toc_buttons() 0 21 4
A get_js_lib() 0 8 2
F first() 0 71 20
D get_package_type() 0 79 19
C create_tree_array() 0 41 12
A get_items_details_as_js() 0 14 4
A get_progress_bar_mode() 0 9 3
B set_terms_by_prefix() 0 68 10
A get_user_id() 0 9 3
A set_use_max_score() 0 19 3
A toggle_visibility() 0 14 2
A get_current_item_id() 0 14 4
A set_previous_item() 0 6 2
A getCurrentBuildingModeURL() 0 10 5
A display_document() 0 20 2
A save_current() 0 32 6
A set_theme() 0 17 3
A getChapterTypes() 0 4 1
F __construct() 0 336 54
B get_preview_image_path() 0 28 7
B restart() 0 40 6
A set_author() 0 16 3
A get_iv_interactions_array() 0 42 4
A set_attempt_mode() 0 32 5
A close() 0 16 3
A update_display_order() 0 29 5
B generate_lp_folder() 0 59 6
F display_quiz_form() 0 192 36
C get_mediaplayer() 0 79 11
A set_proximity() 0 21 4
C display_item() 0 98 16
B overview() 0 54 10
A set_expired_on() 0 31 5
F autocomplete_parents() 0 105 18
B move_up() 0 51 8
A get_interactions_count_from_db() 0 16 2
B get_scorm_prereq_string() 0 73 11
B print_recursive() 0 40 10
B display_move_item() 0 55 11
F display_hotpotatoes_form() 0 180 37
B get_scorm_xml_node() 0 19 7
A has_audio() 0 14 4
A set_encoding() 0 23 5
B edit_item_prereq() 0 46 10
B get_js_info() 0 44 6
A get_id() 0 6 2
B get_js_dropdown_array() 0 74 6
B build_action_menu() 0 117 5
A get_next_item_id() 0 20 6
B get_view() 0 41 6
F is_lp_visible_for_student() 0 127 22
A get_flat_ordered_items_list() 0 35 5
A getProgress() 0 22 2
A display_resources() 0 49 1
F display_manipulate() 0 110 15
A get_complete_items_count() 0 27 6
A get_objectives_count_from_db() 0 16 2
B delete_item() 0 73 9
F edit_document() 0 82 17
F move_item() 0 153 27
A getTotalItemsCountWithoutDirs() 0 14 4
F display_forum_form() 0 205 40
B update_scorm_debug() 0 30 6
A toggleCategoryVisibility() 0 29 3
C getParentToc() 0 67 14
F add_lp() 0 147 14
B update_reinit() 0 30 6
F getListArrayToc() 0 79 12
A get_extension() 0 5 1
A get_view_id() 0 9 3
B toggleCategoryPublish() 0 88 9
D get_navigation_bar() 0 76 11
A generate_learning_path_folder() 0 28 3
A set_hide_toc_frame() 0 20 4
B get_next_index() 0 32 10
B get_attempt_mode() 0 21 9
A get_common_index_terms_by_prefix() 0 17 3
A get_lp_session_id() 0 9 3
A return_new_tree() 0 35 4
A get_previous_item_id() 0 8 2
A get_toc() 0 27 5
C categoryIsVisibleForStudent() 0 68 13
F delete() 0 111 18
A set_maker() 0 20 4
A get_name() 0 9 3
F display_student_publication_form() 0 175 30
A delete_children_items() 0 23 5
B save_item() 0 47 9
A set_name() 0 34 5
F processBuildMenuElements() 0 428 52
A getCategory() 0 7 1
A set_autolaunch() 0 27 2
B getCalculateScore() 0 47 6
A copy() 0 26 1
A getCategoryFromCourseIntoSelect() 0 15 4
A getAccumulateScormTime() 0 3 1
A createCategory() 0 18 1
B get_exercises() 0 104 5
C fixBlockedLinks() 0 64 11
A createForum() 0 21 1
B set_previous_step_as_prerequisite_for_all_items() 0 48 7
A updateCategory() 0 9 2
A display_lp_prerequisites_list() 0 31 5
A getForum() 0 44 3
A getExercisesItems() 0 13 3
B getFinalItemForm() 0 91 4
A getCountCategories() 0 10 2
A moveDownCategory() 0 11 2
F scormExport() 0 974 114
A select_previous_item_id() 0 25 2
A getItem() 0 7 3
A getCategoryId() 0 3 1
B get_links() 0 109 6
A create_path() 0 14 5
A setSubscribeUsers() 0 13 2
B upload_image() 0 40 6
C rl_get_resource_name() 0 100 14
A getSavedFinalItem() 0 12 3
A format_scorm_type_item() 0 3 1
A getCurrentItemParentNames() 0 14 3
A getSubscribeUsers() 0 3 1
A deleteCategory() 0 32 4
A moveUpCategory() 0 11 2
F display_item_prerequisites_form() 0 186 19
A clear_prerequisites() 0 17 2
C scorm_export_to_pdf() 0 63 12
B isFirstOrLastItem() 0 36 7
C getCalculateStars() 0 80 12
A getFinalEvaluationItem() 0 12 3
A getCategoryByCourse() 0 8 1
A getCategoryLinkForTool() 0 12 1
A getSubscriptionSettings() 0 14 2
B verify_document_size() 0 21 8
B get_documents() 0 99 2
C get_forums() 0 112 11
A delete_lp_image() 0 17 5
A getFinalItem() 0 12 4
A setAccumulateScormTime() 0 14 2
A getFinalItemTemplate() 0 3 1
F rl_get_resource_link_for_learnpath() 0 172 32
A getLpFromSession() 0 28 5
A getCategories() 0 17 1
A setCategoryId() 0 10 1
F exportToCourseBuildFormat() 0 199 31
A get_level_for_item() 0 11 3
A get_student_publications() 0 41 3
A lpHasForum() 0 26 1

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

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

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

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
8150
            if ($arrLP[$i]['parent_item_id'] == $parent &&
8151
                $arrLP[$i]['id'] != $id
8152
            ) {
8153
                $selectPrevious->addOption(
8154
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8155
                    $arrLP[$i]['id']
8156
                );
8157
8158
                if (isset($extra_info['previous_item_id']) &&
8159
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8160
                ) {
8161
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8162
                } elseif ($action == 'add') {
8163
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8164
                }
8165
            }
8166
        }
8167
8168
        if ($action != 'move') {
8169
            $id_prerequisite = 0;
8170
            if (is_array($arrLP)) {
8171
                foreach ($arrLP as $key => $value) {
8172
                    if ($value['id'] == $id) {
8173
                        $id_prerequisite = $value['prerequisite'];
8174
                        break;
8175
                    }
8176
                }
8177
            }
8178
8179
            $arrHide = [];
8180
            for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

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

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

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