learnpath   F
last analyzed

Complexity

Total Complexity 2055

Size/Duplication

Total Lines 14914
Duplicated Lines 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
wmc 2055
eloc 8276
c 3
b 1
f 0
dl 0
loc 14914
rs 0.8

238 Methods

Rating   Name   Duplication   Size   Complexity  
A get_course_int_id() 0 3 2
A set_course_int_id() 0 3 1
A getCourseCode() 0 3 1
A getProgressBar() 0 5 1
A getHideTableOfContents() 0 3 1
A get_progress_bar() 0 12 1
B get_scorm_prereq_string() 0 73 11
A get_objectives_count_from_db() 0 16 2
A returnLpItemList() 0 15 2
A update_display_order() 0 29 5
F autocomplete_parents() 0 101 17
A close() 0 13 2
F add_item() 0 238 17
F __construct() 0 317 51
F add_lp() 0 156 13
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 137 22
A set_autolaunch() 0 27 2
A get_total_items_count() 0 3 1
B getCalculateScore() 0 47 6
F save_last() 0 89 23
A get_first_item_id() 0 8 2
B isUserSubscribedToLp() 0 51 8
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
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
F edit_item() 0 235 22
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
D stop_previous_item() 0 55 18
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 saveTheNextLp() 0 5 1
A set_publicated_on() 0 22 3
A getUserIdentifierForExternalServices() 0 11 3
B set_current_item() 0 33 10
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 get_last() 0 10 2
A categoryIsPublished() 0 24 2
A get_type() 0 8 3
A get_maker() 0 7 2
A getNextLpsAvailable() 0 21 4
A getForum() 0 44 3
A getFlowPrevLpId() 0 11 1
A getLpNameById() 0 8 1
A set_preview_image() 0 11 1
A getExercisesItems() 0 13 3
B getFinalItemForm() 0 89 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 get_type_static() 0 16 3
A moveDownCategory() 0 9 2
A sort_tree_array() 0 12 3
F scormExport() 0 984 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 get_previous_index() 0 16 5
A getItemsForForm() 0 39 3
A set_seriousgame_mode() 0 23 4
A get_preview_image() 0 7 2
C isBlockedByPrerequisite() 0 68 13
B get_progress_bar_text() 0 58 11
A get_teacher_toc_buttons() 0 21 4
A get_js_lib() 0 8 2
B getTOCTree() 0 44 8
F first() 0 71 20
A getItem() 0 7 3
A getCategoryId() 0 3 1
B create_tree_array() 0 38 11
A get_items_details_as_js() 0 8 2
B get_links() 0 109 6
A get_progress_bar_mode() 0 7 2
B set_terms_by_prefix() 0 68 10
A get_user_id() 0 7 2
A set_use_max_score() 0 12 1
A toggle_visibility() 0 18 3
A create_path() 0 14 5
A get_current_item_id() 0 8 2
A set_previous_item() 0 6 2
A getCurrentBuildingModeURL() 0 11 5
A display_document() 0 20 2
B upload_image() 0 40 6
A setSubscribeUsers() 0 10 1
A save_current() 0 32 6
A getLastInFirstLevel() 0 13 2
A set_theme() 0 11 1
A getChapterTypes() 0 4 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 getNameNoTags() 0 3 1
B generate_lp_folder() 0 57 8
A getAccumulateWorkTimeTotalCourse() 0 10 1
A moveUpCategory() 0 9 2
C get_mediaplayer() 0 80 13
A set_proximity() 0 15 2
A getProgressFromLpList() 0 32 4
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 print_recursive() 0 43 11
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 8
A has_audio() 0 11 3
A set_encoding() 0 19 4
B edit_item_prereq() 0 41 7
A get_id() 0 7 2
B get_js_dropdown_array() 0 70 6
A getFinalEvaluationItem() 0 12 3
A setItemTitle() 0 14 2
F build_action_menu() 0 179 15
A get_next_item_id() 0 10 3
B get_view() 0 49 7
A getCategoryByCourse() 0 5 1
A getSurveys() 0 34 3
F is_lp_visible_for_student() 0 102 21
A get_flat_ordered_items_list() 0 35 5
A getProgress() 0 23 2
B display_resources() 0 89 5
A getCategoryLinkForTool() 0 12 1
A getFlowLpbuttons() 0 24 6
A getSubscriptionSettings() 0 14 2
B verify_document_size() 0 22 8
B display_manipulate() 0 97 9
B get_complete_items_count() 0 40 8
B get_documents() 0 99 2
B delete_item() 0 70 6
C get_forums() 0 111 11
A setAuthorLpItem() 0 27 6
F edit_document() 0 82 17
D move_item() 0 125 18
A getTotalItemsCountWithoutDirs() 0 16 4
A getCurrentAttempt() 0 10 2
A delete_lp_image() 0 17 5
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
B updateLpProgress() 0 37 6
D getListArrayToc() 0 67 11
A get_view_id() 0 7 2
A get_extension() 0 5 1
F rl_get_resource_link_for_learnpath() 0 260 51
A getAccumulateWorkTimeTotal() 0 11 1
A getLpFromSession() 0 28 5
B toggleCategoryPublish() 0 86 9
A generate_learning_path_folder() 0 28 4
C get_navigation_bar() 0 73 10
A findLastView() 0 35 4
A set_hide_toc_frame() 0 15 2
A getCategories() 0 20 2
B get_next_index() 0 23 7
A getFlowNextLpId() 0 11 1
B get_attempt_mode() 0 21 9
D getPackageType() 0 90 20
A get_common_index_terms_by_prefix() 0 17 3
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
A get_previous_item_id() 0 5 1
F exportToCourseBuildFormat() 0 199 30
A get_toc() 0 18 2
F categoryIsVisibleForStudent() 0 89 19
A get_level_for_item() 0 11 3
F delete() 0 130 19
A set_maker() 0 14 2
A get_student_publications() 0 41 3
A get_name() 0 7 2
A lpHasForum() 0 26 1
A getStatusCSSClassName() 0 7 2
A setAccumulateWorkTime() 0 14 2
A delete_children_items() 0 22 4
B save_item() 0 43 8
A set_name() 0 32 3
F processBuildMenuElements() 0 447 56
F display_thread_form() 0 196 43
F prerequisites_match() 0 80 19
F display_link_form() 0 189 36
F display_document_form() 0 368 76
F displayFrmReadOutText() 0 282 61
F displaySurveyForm() 0 186 41
D prerequistesDatesMatch() 0 43 21
F display_quiz_form() 0 182 37
F display_forum_form() 0 186 41
F display_student_publication_form() 0 167 32

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

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

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

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
9701
            if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
9702
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9703
            ) {
9704
                $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9705
            }
9706
        }
9707
9708
        if (!$no_display_add) {
9709
            $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
9710
            $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
9711
9712
            if ($extra_info == 'new' || $item_type == TOOL_READOUT_TEXT || $edit == 'true') {
9713
                if (!$no_display_edit_textarea) {
9714
                    $content = '';
9715
9716
                    if (isset($_POST['content'])) {
9717
                        $content = stripslashes($_POST['content']);
9718
                    } elseif (is_array($extra_info)) {
9719
                        $content = $this->display_document($extra_info['path'], false, false);
9720
                    } elseif (is_numeric($extra_info)) {
9721
                        $content = $this->display_document($extra_info, false, false);
9722
                    }
9723
9724
                    // A new document, it is in the root of the repository.
9725
                    if (is_array($extra_info) && $extra_info != 'new') {
9726
                    } else {
9727
                        $this->generate_lp_folder($_course);
9728
                    }
9729
9730
                    if ($_GET['action'] == 'add_item') {
9731
                        $text = get_lang('LPCreateDocument');
9732
                    } else {
9733
                        $text = get_lang('SaveDocument');
9734
                    }
9735
9736
                    $form->addTextarea('content_lp', get_lang('Content'), ['rows' => 20]);
9737
                    $form
9738
                        ->defaultRenderer()
9739
                        ->setElementTemplate($form->getDefaultElementTemplate(), 'content_lp');
9740
                    $form->addButtonSave($text, 'submit_button');
9741
                    $defaults['content_lp'] = $content;
9742
                }
9743
            } elseif (is_numeric($extra_info)) {
9744
                $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9745
9746
                $return = $this->display_document($extra_info, true, true, true);
9747
                $form->addElement('html', $return);
9748
            }
9749
        }
9750
9751
        if (is_numeric($extra_info)) {
9752
            $form->addElement('hidden', 'path', $extra_info);
9753
        } elseif (is_array($extra_info)) {
9754
            $form->addElement('hidden', 'path', $extra_info['path']);
9755
        }
9756
9757
        $form->addElement('hidden', 'type', TOOL_READOUT_TEXT);
9758
        $form->addElement('hidden', 'post_time', time());
9759
        $this->setAuthorLpItem($form);
9760
        $form->setDefaults($defaults);
9761
9762
        return $form->returnForm();
9763
    }
9764
9765
    /**
9766
     * @param array  $courseInfo
9767
     * @param string $content
9768
     * @param string $title
9769
     * @param int    $parentId
9770
     *
9771
     * @throws \Doctrine\ORM\ORMException
9772
     * @throws \Doctrine\ORM\OptimisticLockException
9773
     * @throws \Doctrine\ORM\TransactionRequiredException
9774
     *
9775
     * @return int
9776
     */
9777
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
9778
    {
9779
        $creatorId = api_get_user_id();
9780
        $sessionId = api_get_session_id();
9781
9782
        // Generates folder
9783
        $result = $this->generate_lp_folder($courseInfo);
9784
        $dir = $result['dir'];
9785
9786
        if (empty($parentId) || $parentId == '/') {
9787
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
9788
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
9789
9790
            if ($parentId === '/') {
9791
                $dir = '/';
9792
            }
9793
9794
            // Please, do not modify this dirname formatting.
9795
            if (strstr($dir, '..')) {
9796
                $dir = '/';
9797
            }
9798
9799
            if (!empty($dir[0]) && $dir[0] == '.') {
9800
                $dir = substr($dir, 1);
9801
            }
9802
            if (!empty($dir[0]) && $dir[0] != '/') {
9803
                $dir = '/'.$dir;
9804
            }
9805
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
9806
                $dir .= '/';
9807
            }
9808
        } else {
9809
            $parentInfo = DocumentManager::get_document_data_by_id(
9810
                $parentId,
9811
                $courseInfo['code']
9812
            );
9813
            if (!empty($parentInfo)) {
9814
                $dir = $parentInfo['path'].'/';
9815
            }
9816
        }
9817
9818
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9819
9820
        if (!is_dir($filepath)) {
9821
            $dir = '/';
9822
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9823
        }
9824
9825
        $originalTitle = !empty($title) ? $title : $_POST['title'];
9826
9827
        if (!empty($title)) {
9828
            $title = api_replace_dangerous_char(stripslashes($title));
9829
        } else {
9830
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
9831
        }
9832
9833
        $title = disable_dangerous_file($title);
9834
        $filename = $title;
9835
        $content = !empty($content) ? $content : $_POST['content_lp'];
9836
        $tmpFileName = $filename;
9837
9838
        $i = 0;
9839
        while (file_exists($filepath.$tmpFileName.'.html')) {
9840
            $tmpFileName = $filename.'_'.++$i;
9841
        }
9842
9843
        $filename = $tmpFileName.'.html';
9844
        $content = stripslashes($content);
9845
9846
        if (file_exists($filepath.$filename)) {
9847
            return 0;
9848
        }
9849
9850
        $putContent = file_put_contents($filepath.$filename, $content);
9851
9852
        if ($putContent === false) {
9853
            return 0;
9854
        }
9855
9856
        $fileSize = filesize($filepath.$filename);
9857
        $saveFilePath = $dir.$filename;
9858
9859
        $documentId = add_document(
9860
            $courseInfo,
9861
            $saveFilePath,
9862
            'file',
9863
            $fileSize,
9864
            $tmpFileName,
9865
            '',
9866
            0, //readonly
9867
            true,
9868
            null,
9869
            $sessionId,
9870
            $creatorId
9871
        );
9872
9873
        if (!$documentId) {
9874
            return 0;
9875
        }
9876
9877
        api_item_property_update(
9878
            $courseInfo,
9879
            TOOL_DOCUMENT,
9880
            $documentId,
9881
            'DocumentAdded',
9882
            $creatorId,
9883
            null,
9884
            null,
9885
            null,
9886
            null,
9887
            $sessionId
9888
        );
9889
9890
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
9891
        $newTitle = $originalTitle;
9892
9893
        if ($newComment || $newTitle) {
9894
            $em = Database::getManager();
9895
9896
            /** @var CDocument $doc */
9897
            $doc = $em->find('ChamiloCourseBundle:CDocument', $documentId);
9898
9899
            if ($newComment) {
9900
                $doc->setComment($newComment);
9901
            }
9902
9903
            if ($newTitle) {
9904
                $doc->setTitle($newTitle);
9905
            }
9906
9907
            $em->persist($doc);
9908
            $em->flush();
9909
        }
9910
9911
        return $documentId;
9912
    }
9913
9914
    /**
9915
     * Return HTML form to add/edit a link item.
9916
     *
9917
     * @param string $action     (add/edit)
9918
     * @param int    $id         Item ID if exists
9919
     * @param mixed  $extra_info
9920
     *
9921
     * @throws Exception
9922
     * @throws HTML_QuickForm_Error
9923
     *
9924
     * @return string HTML form
9925
     */
9926
    public function display_link_form(
9927
        $action = 'add',
9928
        $id = 0,
9929
        $extra_info = '',
9930
        $item = null,
9931
        $excludeExtraFields = []
9932
    ) {
9933
        $course_id = api_get_course_int_id();
9934
        $tbl_link = Database::get_course_table(TABLE_LINK);
9935
9936
        $item_title = '';
9937
        $item_description = '';
9938
        $item_url = '';
9939
9940
        $previousId = 0;
9941
        if ($id != 0 && is_array($extra_info)) {
9942
            $item_title = stripslashes($extra_info['title']);
9943
            $item_description = stripslashes($extra_info['description']);
9944
            $item_url = stripslashes($extra_info['url']);
9945
            $previousId = $extra_info['previous_item_id'];
9946
        } elseif (is_numeric($extra_info)) {
9947
            $extra_info = (int) $extra_info;
9948
            $sql = "SELECT title, description, url
9949
                    FROM $tbl_link
9950
                    WHERE c_id = $course_id AND iid = $extra_info";
9951
            $result = Database::query($sql);
9952
            $row = Database::fetch_array($result);
9953
            $item_title = $row['title'];
9954
            $item_description = $row['description'];
9955
            $item_url = $row['url'];
9956
        }
9957
9958
        if ($item instanceof learnpathItem) {
9959
            $previousId = $extra_info->previous;
9960
        }
9961
9962
        $form = new FormValidator(
9963
            'edit_link',
9964
            'POST',
9965
            $this->getCurrentBuildingModeURL()
9966
        );
9967
        $defaults = [];
9968
        $parent = 0;
9969
        if ($id != 0 && is_array($extra_info)) {
9970
            $parent = $extra_info['parent_item_id'];
9971
        }
9972
9973
        $arrLP = $this->getItemsForForm();
9974
9975
        $this->tree_array($arrLP);
9976
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9977
        unset($this->arrMenu);
9978
9979
        if ($action === 'add') {
9980
            $legend = get_lang('CreateTheLink');
9981
        } elseif ($action === 'move') {
9982
            $legend = get_lang('MoveCurrentLink');
9983
        } else {
9984
            $legend = get_lang('EditCurrentLink');
9985
        }
9986
9987
        $form->addHeader($legend);
9988
9989
        if ($action !== 'move') {
9990
            $this->setItemTitle($form);
9991
            $defaults['title'] = $item_title;
9992
        }
9993
9994
        $selectParent = $form->addSelect(
9995
            'parent',
9996
            get_lang('Parent'),
9997
            [],
9998
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
9999
        );
10000
        $selectParent->addOption($this->name, 0);
10001
        $arrHide = [
10002
            $id,
10003
        ];
10004
10005
        $parent_item_id = Session::read('parent_item_id', 0);
10006
10007
        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...
10008
            if ($action != 'add') {
10009
                if (
10010
                    ($arrLP[$i]['item_type'] == 'dir') &&
10011
                    !in_array($arrLP[$i]['id'], $arrHide) &&
10012
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
10013
                ) {
10014
                    $selectParent->addOption(
10015
                        $arrLP[$i]['title'],
10016
                        $arrLP[$i]['id'],
10017
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
10018
                    );
10019
10020
                    if ($parent == $arrLP[$i]['id']) {
10021
                        $selectParent->setSelected($arrLP[$i]['id']);
10022
                    }
10023
                } else {
10024
                    $arrHide[] = $arrLP[$i]['id'];
10025
                }
10026
            } else {
10027
                if ($arrLP[$i]['item_type'] == 'dir') {
10028
                    $selectParent->addOption(
10029
                        $arrLP[$i]['title'],
10030
                        $arrLP[$i]['id'],
10031
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
10032
                    );
10033
10034
                    if ($parent_item_id == $arrLP[$i]['id']) {
10035
                        $selectParent->setSelected($arrLP[$i]['id']);
10036
                    }
10037
                }
10038
            }
10039
        }
10040
10041
        if (is_array($arrLP)) {
10042
            reset($arrLP);
10043
        }
10044
10045
        $selectPrevious = $form->addSelect(
10046
            'previous',
10047
            get_lang('Position'),
10048
            [],
10049
            ['id' => 'previous', 'class' => 'learnpath_item_form']
10050
        );
10051
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
10052
10053
        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...
10054
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
10055
                $selectPrevious->addOption(
10056
                    $arrLP[$i]['title'],
10057
                    $arrLP[$i]['id']
10058
                );
10059
10060
                if ($previousId == $arrLP[$i]['id']) {
10061
                    $selectPrevious->setSelected($arrLP[$i]['id']);
10062
                } elseif ($action === 'add') {
10063
                    $selectPrevious->setSelected($arrLP[$i]['id']);
10064
                }
10065
            }
10066
        }
10067
10068
        if ($action !== 'move') {
10069
            $urlAttributes = ['class' => 'learnpath_item_form'];
10070
10071
            if (is_numeric($extra_info)) {
10072
                $urlAttributes['disabled'] = 'disabled';
10073
            }
10074
10075
            $form->addElement('url', 'url', get_lang('Url'), $urlAttributes);
10076
            $defaults['url'] = $item_url;
10077
            $arrHide = [];
10078
            for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

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

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

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