Passed
Push — 1.11.x ( 5b9a4b...89d96a )
by Julito
12:39
created

learnpath   F

Complexity

Total Complexity 1941

Size/Duplication

Total Lines 14277
Duplicated Lines 0 %

Importance

Changes 9
Bugs 0 Features 0
Metric Value
eloc 7911
dl 0
loc 14277
rs 0.8
c 9
b 0
f 0
wmc 1941

227 Methods

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

How to fix   Complexity   

Complex Class

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

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

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

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

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

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

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

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

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

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
14137
                $item_id = $sameParentLpItemList->list[$i]->id;
14138
                // display_order
14139
                $display_order = $i + 1;
14140
                $itemList->setParametersForId($item_id, $display_order, 'display_order');
14141
                // previous_item_id
14142
                $itemList->setParametersForId($item_id, $previous_item_id, 'previous_item_id');
14143
                $previous_item_id = $item_id;
14144
                // next_item_id
14145
                $next_item_id = 0;
14146
                if ($i < count($sameParentLpItemList->list) - 1) {
14147
                    $next_item_id = $sameParentLpItemList->list[$i + 1]->id;
14148
                }
14149
                $itemList->setParametersForId($item_id, $next_item_id, 'next_item_id');
14150
            }
14151
        }
14152
14153
        $table = Database::get_course_table(TABLE_LP_ITEM);
14154
14155
        foreach ($itemList->list as $item) {
14156
            $params = [];
14157
            $params['display_order'] = $item->display_order;
14158
            $params['previous_item_id'] = $item->previous_item_id;
14159
            $params['next_item_id'] = $item->next_item_id;
14160
            $params['parent_item_id'] = $item->parent_item_id;
14161
14162
            Database::update(
14163
                $table,
14164
                $params,
14165
                [
14166
                    'iid = ? AND c_id = ? ' => [
14167
                        (int) $item->id,
14168
                        (int) $courseId,
14169
                    ],
14170
                ]
14171
            );
14172
        }
14173
    }
14174
14175
    /**
14176
     * Get the depth level of LP item.
14177
     *
14178
     * @param array $items
14179
     * @param int   $currentItemId
14180
     *
14181
     * @return int
14182
     */
14183
    private static function get_level_for_item($items, $currentItemId)
14184
    {
14185
        $parentItemId = 0;
14186
        if (isset($items[$currentItemId])) {
14187
            $parentItemId = $items[$currentItemId]->parent;
14188
        }
14189
14190
        if ($parentItemId == 0) {
14191
            return 0;
14192
        } else {
14193
            return self::get_level_for_item($items, $parentItemId) + 1;
14194
        }
14195
    }
14196
14197
    /**
14198
     * Generate the link for a learnpath category as course tool.
14199
     *
14200
     * @param int $categoryId
14201
     *
14202
     * @return string
14203
     */
14204
    private static function getCategoryLinkForTool($categoryId)
14205
    {
14206
        $categoryId = (int) $categoryId;
14207
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
14208
            .http_build_query(
14209
                [
14210
                    'action' => 'view_category',
14211
                    'id' => $categoryId,
14212
                ]
14213
            );
14214
14215
        return $link;
14216
    }
14217
14218
    /**
14219
     * Return the scorm item type object with spaces replaced with _
14220
     * The return result is use to build a css classname like scorm_type_$return.
14221
     *
14222
     * @param $in_type
14223
     *
14224
     * @return mixed
14225
     */
14226
    private static function format_scorm_type_item($in_type)
14227
    {
14228
        return str_replace(' ', '_', $in_type);
14229
    }
14230
14231
    /**
14232
     * Check and obtain the lp final item if exist.
14233
     *
14234
     * @return learnpathItem
14235
     */
14236
    private function getFinalItem()
14237
    {
14238
        if (empty($this->items)) {
14239
            return null;
14240
        }
14241
14242
        foreach ($this->items as $item) {
14243
            if ($item->type !== 'final_item') {
14244
                continue;
14245
            }
14246
14247
            return $item;
14248
        }
14249
    }
14250
14251
    /**
14252
     * Get the LP Final Item Template.
14253
     *
14254
     * @return string
14255
     */
14256
    private function getFinalItemTemplate()
14257
    {
14258
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
14259
    }
14260
14261
    /**
14262
     * Get the LP Final Item Url.
14263
     *
14264
     * @return string
14265
     */
14266
    private function getSavedFinalItem()
14267
    {
14268
        $finalItem = $this->getFinalItem();
14269
        $doc = DocumentManager::get_document_data_by_id(
14270
            $finalItem->path,
14271
            $this->cc
14272
        );
14273
        if ($doc && file_exists($doc['absolute_path'])) {
14274
            return file_get_contents($doc['absolute_path']);
14275
        }
14276
14277
        return '';
14278
    }
14279
14280
    /**
14281
     * Gets the form to evaluate if it exists contains the extra field extra_authorlpitem
14282
     * to establish authors when editing an item of an LP.
14283
     */
14284
    private function setAuthorLpItem(FormValidator $form)
14285
    {
14286
        if ($form->hasElement('extra_authorlpitem')) {
14287
            /** @var HTML_QuickForm_select $author */
14288
            $author = $form->getElement('extra_authorlpitem');
14289
            $options = [];
14290
            $field = new ExtraField('user');
14291
            $authorLp = $field->get_handler_field_info_by_field_variable('authorlp');
14292
            $extraFieldId = isset($authorLp['id']) ? (int) $authorLp['id'] : 0;
14293
            if ($extraFieldId != 0) {
14294
                $extraFieldValueUser = new ExtraFieldValue('user');
14295
                $values = $extraFieldValueUser->get_item_id_from_field_variable_and_field_value(
14296
                    $authorLp['variable'],
14297
                    1,
14298
                    true,
14299
                    false,
14300
                    true
14301
                );
14302
14303
                if (!empty($values)) {
14304
                    foreach ($values as $item) {
14305
                        $teacher = api_get_user_info($item['item_id']);
14306
                        $options[$teacher['id']] = $teacher['complete_name'];
14307
                    }
14308
                }
14309
            }
14310
            $author->setOptions($options);
14311
        }
14312
    }
14313
}
14314