learnpath   F
last analyzed

Complexity

Total Complexity 2065

Size/Duplication

Total Lines 14956
Duplicated Lines 0 %

Importance

Changes 5
Bugs 1 Features 0
Metric Value
wmc 2065
eloc 8302
c 5
b 1
f 0
dl 0
loc 14956
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 next() 0 22 5
B move_down() 0 54 8
B move_up() 0 51 8
A get_type() 0 8 3
A get_type_static() 0 16 3
A get_items_details_as_js() 0 8 2
A getChapterTypes() 0 4 1
A get_flat_ordered_items_list() 0 35 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
A get_total_items_count() 0 3 1
A get_first_item_id() 0 8 2
F edit_item() 0 235 22
A get_last() 0 10 2
A getLpNameById() 0 8 1
A get_js_lib() 0 8 2
F first() 0 71 20
A get_current_item_id() 0 8 2
A getLastInFirstLevel() 0 13 2
B edit_item_prereq() 0 41 7
A get_id() 0 7 2
B get_complete_items_count() 0 40 8
B delete_item() 0 70 6
A getTotalItemsCountWithoutDirs() 0 16 4
A get_common_index_terms_by_prefix() 0 17 3
A delete_children_items() 0 22 4
A getProgressBar() 0 5 1
B update_default_view_mode() 0 33 6
B get_iv_objectives_array() 0 38 6
F save_last() 0 89 23
B isUserSubscribedToLp() 0 51 8
A get_update_queue() 0 3 1
F get_link() 0 351 62
A switch_attempt_mode() 0 16 4
F prerequisites_match() 0 80 19
A previous() 0 11 1
A tree_array() 0 4 1
A open() 0 9 1
A set_jslib() 0 15 2
A update_default_scorm_commit() 0 25 4
B getChildrenToc() 0 49 11
A set_error_msg() 0 9 3
D stop_previous_item() 0 55 18
D start_current_item() 0 45 18
A set_publicated_on() 0 22 3
B set_current_item() 0 33 10
A get_author() 0 7 2
A set_prerequisite() 0 10 1
A set_modified_on() 0 10 1
A getHideTableOfContents() 0 3 1
A categoryIsPublished() 0 24 2
A get_maker() 0 7 2
A get_progress_bar() 0 12 1
A set_preview_image() 0 11 1
F toggle_publish() 0 138 15
A sort_tree_array() 0 12 3
A get_items_status_list() 0 10 2
A get_theme() 0 7 2
A get_previous_index() 0 16 5
A set_seriousgame_mode() 0 23 4
A get_preview_image() 0 7 2
C isBlockedByPrerequisite() 0 68 13
A returnLpItemList() 0 15 2
B get_progress_bar_text() 0 58 11
A get_teacher_toc_buttons() 0 21 4
B getTOCTree() 0 44 8
B create_tree_array() 0 38 11
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
D prerequistesDatesMatch() 0 43 21
A set_previous_item() 0 6 2
A save_current() 0 32 6
A set_theme() 0 11 1
B get_preview_image_path() 0 28 7
B restart() 0 40 6
A set_author() 0 10 1
B get_iv_interactions_array() 0 54 8
A set_attempt_mode() 0 32 5
A update_display_order() 0 29 5
A getNameNoTags() 0 3 1
C get_mediaplayer() 0 80 13
A set_proximity() 0 15 2
A getProgressFromLpList() 0 32 4
B overview() 0 50 9
A set_expired_on() 0 23 3
A get_interactions_count_from_db() 0 16 2
B get_scorm_prereq_string() 0 73 11
B print_recursive() 0 43 11
B get_scorm_xml_node() 0 19 8
A has_audio() 0 11 3
A set_encoding() 0 19 4
A get_next_item_id() 0 10 3
B get_view() 0 49 7
F is_lp_visible_for_student() 0 111 22
A getProgress() 0 23 2
A getFlowLpbuttons() 0 24 6
A get_objectives_count_from_db() 0 16 2
D move_item() 0 125 18
A update_scorm_debug() 0 23 4
A toggleCategoryVisibility() 0 14 2
C getParentToc() 0 54 13
A update_reinit() 0 23 4
B updateLpProgress() 0 37 6
D getListArrayToc() 0 67 11
A get_view_id() 0 7 2
B toggleCategoryPublish() 0 86 9
C get_navigation_bar() 0 83 10
A set_hide_toc_frame() 0 15 2
B get_next_index() 0 23 7
B get_attempt_mode() 0 21 9
D getPackageType() 0 90 20
A get_lp_session_id() 0 7 2
A return_new_tree() 0 34 4
A get_previous_item_id() 0 5 1
A get_toc() 0 18 2
F categoryIsVisibleForStudent() 0 88 19
A set_maker() 0 14 2
A get_name() 0 7 2
A getStatusCSSClassName() 0 7 2
B save_item() 0 43 8
A set_name() 0 32 3
F processBuildMenuElements() 0 447 56
F delete() 0 130 19
A getCategory() 0 6 1
F display_edit_item() 0 137 22
A set_autolaunch() 0 27 2
F display_thread_form() 0 196 43
B getCalculateScore() 0 47 6
A copy() 0 24 1
B getCategoryFromCourseIntoSelect() 0 32 7
A getAccumulateScormTime() 0 3 1
F create_document() 0 169 30
A createCategory() 0 27 3
F get_exercises() 0 162 13
C fixBlockedLinks() 0 64 11
A createForum() 0 21 1
A getLpList() 0 27 3
A getCategorySessionId() 0 18 3
B set_previous_step_as_prerequisite_for_all_items() 0 48 7
A updateCategory() 0 9 2
A saveTheNextLp() 0 5 1
A getUserIdentifierForExternalServices() 0 11 3
A getAccumulateWorkTime() 0 3 1
A display_lp_prerequisites_list() 0 31 5
F display_link_form() 0 189 36
F display_document_form() 0 368 76
A getNextLpsAvailable() 0 21 4
A getForum() 0 44 3
A getFlowPrevLpId() 0 11 1
A getExercisesItems() 0 13 3
B getFinalItemForm() 0 89 4
A getSelectedIcon() 0 10 3
B sortItemByOrderList() 0 48 7
A getCountCategories() 0 10 2
F createReadOutText() 0 135 27
A moveDownCategory() 0 9 2
F displayFrmReadOutText() 0 282 61
F displaySurveyForm() 0 186 41
F scormExport() 0 984 114
A getAccumulateWorkTimePrerequisite() 0 13 1
A getSelectedIconHtml() 0 12 2
F display_item_form() 0 239 38
A select_previous_item_id() 0 22 1
A getItemsForForm() 0 39 3
A getItem() 0 7 3
A getCategoryId() 0 3 1
B get_links() 0 109 6
A create_path() 0 14 5
A getCurrentBuildingModeURL() 0 11 5
A display_document() 0 20 2
B upload_image() 0 40 6
A setSubscribeUsers() 0 10 1
C rl_get_resource_name() 0 92 14
A getSavedFinalItem() 0 12 3
A cleanItemTitle() 0 5 1
A format_scorm_type_item() 0 3 1
A getCurrentItemParentNames() 0 13 3
A getSubscribeUsers() 0 3 1
A deleteCategory() 0 47 5
B generate_lp_folder() 0 57 8
A getAccumulateWorkTimeTotalCourse() 0 10 1
F display_quiz_form() 0 182 37
A moveUpCategory() 0 9 2
C display_item() 0 98 16
F display_item_prerequisites_form() 0 183 21
A clear_prerequisites() 0 14 1
C scorm_export_to_pdf() 0 70 12
B isFirstOrLastItem() 0 32 6
B display_move_item() 0 52 11
C getCalculateStars() 0 80 12
F display_hotpotatoes_form() 0 155 36
B get_js_dropdown_array() 0 70 6
A getFinalEvaluationItem() 0 12 3
A setItemTitle() 0 14 2
F build_action_menu() 0 182 18
A getCategoryByCourse() 0 5 1
A getSurveys() 0 34 3
B display_resources() 0 89 5
A getCategoryLinkForTool() 0 12 1
A getSubscriptionSettings() 0 14 2
B verify_document_size() 0 22 8
B display_manipulate() 0 97 9
B get_documents() 0 99 2
C get_forums() 0 111 11
A setAuthorLpItem() 0 27 6
F edit_document() 0 82 17
A getCurrentAttempt() 0 10 2
A delete_lp_image() 0 17 5
F display_forum_form() 0 186 41
A getUseScoreAsProgress() 0 18 5
A getFinalItem() 0 12 4
A setAccumulateScormTime() 0 11 1
A getFinalItemTemplate() 0 3 1
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
A generate_learning_path_folder() 0 28 4
A findLastView() 0 35 4
A getCategories() 0 20 2
A getFlowNextLpId() 0 11 1
A setCategoryId() 0 10 1
A getIconSelect() 0 19 4
F exportToCourseBuildFormat() 0 199 30
A get_level_for_item() 0 11 3
A get_student_publications() 0 41 3
A lpHasForum() 0 26 1
F display_student_publication_form() 0 167 32
A setAccumulateWorkTime() 0 14 2

How to fix   Complexity   

Complex Class

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

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

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

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\Repository\CourseRepository;
6
use Chamilo\CoreBundle\Entity\Repository\ItemPropertyRepository;
7
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
8
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
9
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
10
use Chamilo\CourseBundle\Entity\CDocument;
11
use Chamilo\CourseBundle\Entity\CItemProperty;
12
use Chamilo\CourseBundle\Entity\CLp;
13
use Chamilo\CourseBundle\Entity\CLpCategory;
14
use Chamilo\CourseBundle\Entity\CLpItem;
15
use Chamilo\CourseBundle\Entity\CLpItemView;
16
use Chamilo\CourseBundle\Entity\CTool;
17
use Chamilo\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
        $lessonsText = get_lang('LearningPathList');
1915
1916
        $settings = api_get_configuration_value('lp_view_settings');
1917
        $display = isset($settings['display']) ? $settings['display'] : false;
1918
        $reportingIcon = '
1919
        <a class="icon-toolbar"
1920
            id="stats_link"
1921
            href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
1922
            onclick="window.parent.API.save_asset(); return true;"
1923
            target="content_name" title="'.$reportingText.'">
1924
            <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1925
        </a>';
1926
1927
        if (!empty($display)) {
1928
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1929
            if ($showReporting === false) {
1930
                $reportingIcon = '';
1931
            }
1932
        }
1933
1934
        $hideArrows = false;
1935
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1936
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1937
        }
1938
1939
        $previousIcon = '';
1940
        $nextIcon = '';
1941
        if ($hideArrows === false) {
1942
            $previousIcon = '
1943
            <a class="icon-toolbar" id="scorm-previous" href="#"
1944
                onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1945
                <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1946
            </a>';
1947
1948
            $nextIcon = '
1949
            <a class="icon-toolbar" id="scorm-next" href="#"
1950
                onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1951
                <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1952
            </a>';
1953
        }
1954
1955
        $lessonsIcon = '
1956
        <a class="icon-toolbar" id="lessons-link" href="lp_controller.php?'.api_get_cidreq(true).'&reduced=true&isStudentView=true&hide_course_breadcrumb=true"
1957
            onclick="window.parent.API.save_asset(); return true;"
1958
            target="content_name" title="'.$lessonsText.'">
1959
            <span class="fa fa-star"></span><span class="sr-only">'.$lessonsText.'</span>
1960
        </a>';
1961
1962
        if ($this->mode === 'fullscreen') {
1963
            $navbar = '
1964
              <span id="'.$barId.'" class="buttons">
1965
                '.$reportingIcon.'
1966
                '.$previousIcon.'
1967
                '.$nextIcon.'
1968
                '.$lessonsIcon.'
1969
                <a class="icon-toolbar" id="view-embedded"
1970
                    href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1971
                    <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1972
                </a>
1973
              </span>';
1974
        } else {
1975
            $navbar = '
1976
             <span id="'.$barId.'" class="buttons text-right">
1977
                '.$reportingIcon.'
1978
                '.$previousIcon.'
1979
                '.$nextIcon.'
1980
                '.$lessonsIcon.'
1981
            </span>';
1982
        }
1983
1984
        return $navbar;
1985
    }
1986
1987
    /**
1988
     * Gets the next resource in queue (url).
1989
     *
1990
     * @return string URL to load into the viewer
1991
     */
1992
    public function get_next_index()
1993
    {
1994
        // TODO
1995
        $index = $this->index;
1996
        $index++;
1997
        while (
1998
            !empty($this->ordered_items[$index]) && ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') &&
1999
            $index < $this->max_ordered_items
2000
        ) {
2001
            $index++;
2002
            if ($index == $this->max_ordered_items) {
2003
                if ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') {
2004
                    return $this->index;
2005
                }
2006
2007
                return $index;
2008
            }
2009
        }
2010
        if (empty($this->ordered_items[$index])) {
2011
            return $this->index;
2012
        }
2013
2014
        return $index;
2015
    }
2016
2017
    /**
2018
     * Gets item_id for the next element.
2019
     *
2020
     * @return int Next item (DB) ID
2021
     */
2022
    public function get_next_item_id()
2023
    {
2024
        $new_index = $this->get_next_index();
2025
        if (!empty($new_index)) {
2026
            if (isset($this->ordered_items[$new_index])) {
2027
                return $this->ordered_items[$new_index];
2028
            }
2029
        }
2030
2031
        return 0;
2032
    }
2033
2034
    /**
2035
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
2036
     *
2037
     * Generally, the package provided is in the form of a zip file, so the function
2038
     * has been written to test a zip file. If not a zip, the function will return the
2039
     * default return value: ''
2040
     *
2041
     * @param string $file_path the path to the file
2042
     * @param string $file_name the original name of the file
2043
     *
2044
     * @return string 'scorm','aicc','scorm2004','dokeos', 'error-empty-package' if the package is empty, or '' if the package cannot be recognized
2045
     */
2046
    public static function getPackageType($file_path, $file_name)
2047
    {
2048
        // Get name of the zip file without the extension.
2049
        $file_info = pathinfo($file_name);
2050
        $extension = $file_info['extension']; // Extension only.
2051
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
2052
                'dll',
2053
                'exe',
2054
            ])) {
2055
            return 'oogie';
2056
        }
2057
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
2058
                'dll',
2059
                'exe',
2060
            ])) {
2061
            return 'woogie';
2062
        }
2063
2064
        $zipFile = new PclZip($file_path);
2065
        // Check the zip content (real size and file extension).
2066
        $zipContentArray = $zipFile->listContent();
2067
        $package_type = '';
2068
        $manifest = '';
2069
        $aicc_match_crs = 0;
2070
        $aicc_match_au = 0;
2071
        $aicc_match_des = 0;
2072
        $aicc_match_cst = 0;
2073
        $countItems = 0;
2074
2075
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
2076
        if (is_array($zipContentArray)) {
2077
            $countItems = count($zipContentArray);
2078
            if ($countItems > 0) {
2079
                foreach ($zipContentArray as $thisContent) {
2080
                    if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
2081
                        // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
2082
                    } elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) {
2083
                        $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
2084
                        $package_type = 'scorm';
2085
                        break; // Exit the foreach loop.
2086
                    } elseif (
2087
                        preg_match('/aicc\//i', $thisContent['filename']) ||
2088
                        in_array(
2089
                            strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
2090
                            ['crs', 'au', 'des', 'cst']
2091
                        )
2092
                    ) {
2093
                        $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
2094
                        switch ($ext) {
2095
                            case 'crs':
2096
                                $aicc_match_crs = 1;
2097
                                break;
2098
                            case 'au':
2099
                                $aicc_match_au = 1;
2100
                                break;
2101
                            case 'des':
2102
                                $aicc_match_des = 1;
2103
                                break;
2104
                            case 'cst':
2105
                                $aicc_match_cst = 1;
2106
                                break;
2107
                            default:
2108
                                break;
2109
                        }
2110
                        //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
2111
                    } else {
2112
                        $package_type = '';
2113
                    }
2114
                }
2115
            }
2116
        }
2117
2118
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
2119
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
2120
            $package_type = 'aicc';
2121
        }
2122
2123
        // Try with chamilo course builder
2124
        if (empty($package_type)) {
2125
            // Sometimes users will try to upload an empty zip, or a zip with
2126
            // only a folder. Catch that and make the calling function aware.
2127
            // If the single file was the imsmanifest.xml, then $package_type
2128
            // would be 'scorm' and we wouldn't be here.
2129
            if ($countItems < 2) {
2130
                return 'error-empty-package';
2131
            }
2132
            $package_type = 'chamilo';
2133
        }
2134
2135
        return $package_type;
2136
    }
2137
2138
    /**
2139
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
2140
     *
2141
     * @return string URL to load into the viewer
2142
     */
2143
    public function get_previous_index()
2144
    {
2145
        $index = $this->index;
2146
        if (isset($this->ordered_items[$index - 1])) {
2147
            $index--;
2148
            while (isset($this->ordered_items[$index]) &&
2149
                ($this->items[$this->ordered_items[$index]]->get_type() == 'dir')
2150
            ) {
2151
                $index--;
2152
                if ($index < 0) {
2153
                    return $this->index;
2154
                }
2155
            }
2156
        }
2157
2158
        return $index;
2159
    }
2160
2161
    /**
2162
     * Gets item_id for the next element.
2163
     *
2164
     * @return int Previous item (DB) ID
2165
     */
2166
    public function get_previous_item_id()
2167
    {
2168
        $index = $this->get_previous_index();
2169
2170
        return $this->ordered_items[$index];
2171
    }
2172
2173
    /**
2174
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2175
     *
2176
     * @param int    $lpItemId
2177
     * @param string $autostart
2178
     *
2179
     * @return string The mediaplayer HTML
2180
     */
2181
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2182
    {
2183
        $course_id = api_get_course_int_id();
2184
        $courseInfo = api_get_course_info();
2185
        $lpItemId = (int) $lpItemId;
2186
2187
        if (empty($courseInfo) || empty($lpItemId)) {
2188
            return '';
2189
        }
2190
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
2191
2192
        if (empty($item)) {
2193
            return '';
2194
        }
2195
2196
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2197
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2198
        $itemViewId = (int) $item->db_item_view_id;
2199
2200
        // Getting all the information about the item.
2201
        $sql = "SELECT lp_view.status
2202
                FROM $tbl_lp_item as lpi
2203
                INNER JOIN $tbl_lp_item_view as lp_view
2204
                ON (lpi.iid = lp_view.lp_item_id)
2205
                WHERE
2206
                    lp_view.iid = $itemViewId AND
2207
                    lpi.iid = $lpItemId AND
2208
                    lp_view.c_id = $course_id";
2209
        $result = Database::query($sql);
2210
        $row = Database::fetch_assoc($result);
2211
        $output = '';
2212
        $audio = $item->audio;
2213
2214
        if (!empty($audio)) {
2215
            $list = $_SESSION['oLP']->get_toc();
2216
2217
            switch ($item->get_type()) {
2218
                case 'quiz':
2219
                    $type_quiz = false;
2220
                    foreach ($list as $toc) {
2221
                        if ($toc['id'] == $_SESSION['oLP']->current) {
2222
                            $type_quiz = true;
2223
                        }
2224
                    }
2225
2226
                    if ($type_quiz) {
2227
                        if ($_SESSION['oLP']->prevent_reinit == 1) {
2228
                            $autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
2229
                        } else {
2230
                            $autostart_audio = $autostart;
2231
                        }
2232
                    }
2233
                    break;
2234
                case TOOL_READOUT_TEXT:
2235
                    $autostart_audio = 'false';
2236
                    break;
2237
                default:
2238
                    $autostart_audio = 'true';
2239
            }
2240
2241
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'.$audio;
2242
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document'.$audio.'?'.api_get_cidreq();
2243
2244
            $player = Display::getMediaPlayer(
2245
                $file,
2246
                [
2247
                    'id' => 'lp_audio_media_player',
2248
                    'url' => $url,
2249
                    'autoplay' => $autostart_audio,
2250
                    'width' => '100%',
2251
                ]
2252
            );
2253
2254
            // The mp3 player.
2255
            $output = '<div id="container">';
2256
            $output .= $player;
2257
            $output .= '</div>';
2258
        }
2259
2260
        return $output;
2261
    }
2262
2263
    /**
2264
     * @param int   $studentId
2265
     * @param int   $prerequisite
2266
     * @param array $courseInfo
2267
     * @param int   $sessionId
2268
     *
2269
     * @return bool
2270
     */
2271
    public static function isBlockedByPrerequisite(
2272
        $studentId,
2273
        $prerequisite,
2274
        $courseInfo,
2275
        $sessionId
2276
    ) {
2277
        if (empty($courseInfo)) {
2278
            return false;
2279
        }
2280
2281
        $courseId = $courseInfo['real_id'];
2282
2283
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2284
        if ($allow) {
2285
            if (api_is_allowed_to_edit() ||
2286
                api_is_platform_admin(true) ||
2287
                api_is_drh() ||
2288
                api_is_coach($sessionId, $courseId, false)
2289
            ) {
2290
                return false;
2291
            }
2292
        }
2293
2294
        $isBlocked = false;
2295
        if (!empty($prerequisite)) {
2296
            $progress = self::getProgress(
2297
                $prerequisite,
2298
                $studentId,
2299
                $courseId,
2300
                $sessionId
2301
            );
2302
            if ($progress < 100) {
2303
                $isBlocked = true;
2304
            }
2305
2306
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2307
                // Block if it does not exceed minimum time
2308
                // Minimum time (in minutes) to pass the learning path
2309
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
2310
2311
                if ($accumulateWorkTime > 0) {
2312
                    // Total time in course (sum of times in learning paths from course)
2313
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
2314
2315
                    // Connect with the plugin_licences_course_session table
2316
                    // which indicates what percentage of the time applies
2317
                    // Minimum connection percentage
2318
                    $perc = 100;
2319
                    // Time from the course
2320
                    $tc = $accumulateWorkTimeTotal;
2321
2322
                    // Percentage of the learning paths
2323
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
2324
                    // Minimum time for each learning path
2325
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
2326
2327
                    // Spent time (in seconds) so far in the learning path
2328
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
2329
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
2330
2331
                    if ($lpTime < ($accumulateWorkTime * 60)) {
2332
                        $isBlocked = true;
2333
                    }
2334
                }
2335
            }
2336
        }
2337
2338
        return $isBlocked;
2339
    }
2340
2341
    /**
2342
     * Checks if the learning path is visible for student after the progress
2343
     * of its prerequisite is completed, considering the time availability and
2344
     * the LP visibility.
2345
     *
2346
     * @param int   $lp_id
2347
     * @param int   $student_id
2348
     * @param array $courseInfo
2349
     * @param int   $sessionId
2350
     * @param bool  $checkSubscription Optional. Allow don't check if user is subscribed to the LP.
2351
     *
2352
     * @return bool
2353
     */
2354
    public static function is_lp_visible_for_student(
2355
        $lp_id,
2356
        $student_id,
2357
        $courseInfo = [],
2358
        $sessionId = 0,
2359
        bool $checkSubscription = true
2360
    ) {
2361
        $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
2362
        $lp_id = (int) $lp_id;
2363
        $sessionId = (int) $sessionId;
2364
2365
        if (empty($courseInfo)) {
2366
            return false;
2367
        }
2368
2369
        if (empty($sessionId)) {
2370
            $sessionId = api_get_session_id();
2371
        }
2372
2373
        $courseId = $courseInfo['real_id'];
2374
2375
        $itemInfo = api_get_item_property_info(
2376
            $courseId,
2377
            TOOL_LEARNPATH,
2378
            $lp_id,
2379
            $sessionId
2380
        );
2381
        // If there is no registry for the session verify the registry in the base course
2382
        if (empty($itemInfo)) {
2383
            $itemInfo = api_get_item_property_info(
2384
                $courseId,
2385
                TOOL_LEARNPATH,
2386
                $lp_id,
2387
                0
2388
            );
2389
        }
2390
2391
        // If the item was deleted or is invisible.
2392
        if (isset($itemInfo['visibility']) && ($itemInfo['visibility'] == 2 || $itemInfo['visibility'] == 0)) {
2393
            return false;
2394
        }
2395
2396
        // @todo remove this query and load the row info as a parameter
2397
        $table = Database::get_course_table(TABLE_LP_MAIN);
2398
        // Get current prerequisite
2399
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on, category_id
2400
                FROM $table
2401
                WHERE iid = $lp_id";
2402
        $rs = Database::query($sql);
2403
        $now = time();
2404
        if (Database::num_rows($rs) > 0) {
2405
            $row = Database::fetch_array($rs, 'ASSOC');
2406
            if (!empty($row['category_id'])) {
2407
                $category = self::getCategory($row['category_id']);
2408
                if (self::categoryIsVisibleForStudent($category, api_get_user_entity($student_id)) === false) {
2409
                    return false;
2410
                }
2411
            }
2412
2413
            $prerequisite = $row['prerequisite'];
2414
            $is_visible = true;
2415
2416
            $isBlocked = self::isBlockedByPrerequisite(
2417
                $student_id,
2418
                $prerequisite,
2419
                $courseInfo,
2420
                $sessionId
2421
            );
2422
2423
            if ($isBlocked) {
2424
                $is_visible = false;
2425
            }
2426
2427
            // Also check the time availability of the LP
2428
            if ($is_visible) {
2429
                // Adding visibility restrictions
2430
                if (!empty($row['publicated_on'])) {
2431
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2432
                        $is_visible = false;
2433
                    }
2434
                }
2435
                // Blocking empty start times see BT#2800
2436
                global $_custom;
2437
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2438
                    $_custom['lps_hidden_when_no_start_date']
2439
                ) {
2440
                    if (empty($row['publicated_on'])) {
2441
                        $is_visible = false;
2442
                    }
2443
                }
2444
2445
                if (!empty($row['expired_on'])) {
2446
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2447
                        $is_visible = false;
2448
                    }
2449
                }
2450
            }
2451
2452
            if ($is_visible && $checkSubscription) {
2453
                $is_visible = self::isUserSubscribedToLp(
2454
                    $row,
2455
                    (int) $student_id,
2456
                    $courseInfo,
2457
                    (int) $sessionId
2458
                );
2459
            }
2460
2461
            return $is_visible;
2462
        }
2463
2464
        return false;
2465
    }
2466
2467
    public static function isUserSubscribedToLp(
2468
        array $lpInfo,
2469
        int $studentId,
2470
        array $courseInfo,
2471
        int $sessionId = 0
2472
    ): bool {
2473
        $subscriptionSettings = self::getSubscriptionSettings();
2474
2475
        // Check if the subscription users/group to a LP is ON
2476
        if (isset($lpInfo['subscribe_users']) && $lpInfo['subscribe_users'] == 1 &&
2477
            $subscriptionSettings['allow_add_users_to_lp'] === true
2478
        ) {
2479
            // Checking only the user visibility
2480
            $userVisibility = api_get_item_visibility(
2481
                $courseInfo,
2482
                'learnpath',
2483
                $lpInfo['id'],
2484
                $sessionId,
2485
                $studentId,
2486
                'LearnpathSubscription'
2487
            );
2488
2489
            if (1 == $userVisibility) {
2490
                return true;
2491
            }
2492
2493
            // Try group
2494
            $userGroups = GroupManager::getAllGroupPerUserSubscription($studentId, $courseInfo['real_id']);
2495
2496
            if (!empty($userGroups)) {
2497
                foreach ($userGroups as $groupInfo) {
2498
                    $userVisibility = api_get_item_visibility(
2499
                        $courseInfo,
2500
                        'learnpath',
2501
                        $lpInfo['id'],
2502
                        $sessionId,
2503
                        null,
2504
                        'LearnpathSubscription',
2505
                        $groupInfo['iid']
2506
                    );
2507
2508
                    if (1 == $userVisibility) {
2509
                        return true;
2510
                    }
2511
                }
2512
            }
2513
2514
            return false;
2515
        }
2516
2517
        return true;
2518
    }
2519
2520
    /**
2521
     * @param int $lpId
2522
     * @param int $userId
2523
     * @param int $courseId
2524
     * @param int $sessionId
2525
     *
2526
     * @return int
2527
     */
2528
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2529
    {
2530
        $lpId = (int) $lpId;
2531
        $userId = (int) $userId;
2532
        $courseId = (int) $courseId;
2533
        $sessionId = (int) $sessionId;
2534
2535
        $sessionCondition = api_get_session_condition($sessionId);
2536
        $table = Database::get_course_table(TABLE_LP_VIEW);
2537
        $sql = "SELECT progress FROM $table
2538
                WHERE
2539
                    c_id = $courseId AND
2540
                    lp_id = $lpId AND
2541
                    user_id = $userId $sessionCondition ";
2542
        $res = Database::query($sql);
2543
2544
        $progress = 0;
2545
        if (Database::num_rows($res) > 0) {
2546
            $row = Database::fetch_array($res);
2547
            $progress = (int) $row['progress'];
2548
        }
2549
2550
        return $progress;
2551
    }
2552
2553
    /**
2554
     * @param array $lpList
2555
     * @param int   $userId
2556
     * @param int   $courseId
2557
     * @param int   $sessionId
2558
     *
2559
     * @return array
2560
     */
2561
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2562
    {
2563
        $lpList = array_map('intval', $lpList);
2564
        if (empty($lpList)) {
2565
            return [];
2566
        }
2567
2568
        $lpList = implode("','", $lpList);
2569
2570
        $userId = (int) $userId;
2571
        $courseId = (int) $courseId;
2572
        $sessionId = (int) $sessionId;
2573
2574
        $sessionCondition = api_get_session_condition($sessionId);
2575
        $table = Database::get_course_table(TABLE_LP_VIEW);
2576
        $sql = "SELECT lp_id, progress FROM $table
2577
                WHERE
2578
                    c_id = $courseId AND
2579
                    lp_id IN ('".$lpList."') AND
2580
                    user_id = $userId $sessionCondition ";
2581
        $res = Database::query($sql);
2582
2583
        if (Database::num_rows($res) > 0) {
2584
            $list = [];
2585
            while ($row = Database::fetch_array($res)) {
2586
                $list[$row['lp_id']] = $row['progress'];
2587
            }
2588
2589
            return $list;
2590
        }
2591
2592
        return [];
2593
    }
2594
2595
    /**
2596
     * Displays a progress bar
2597
     * completed so far.
2598
     *
2599
     * @param int    $percentage Progress value to display
2600
     * @param string $text_add   Text to display near the progress value
2601
     *
2602
     * @return string HTML string containing the progress bar
2603
     */
2604
    public static function get_progress_bar($percentage = -1, $text_add = '')
2605
    {
2606
        $text = $percentage.$text_add;
2607
        $output = '<div class="progress">
2608
            <div id="progress_bar_value"
2609
                class="progress-bar progress-bar-success" role="progressbar"
2610
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2611
            '.$text.'
2612
            </div>
2613
        </div>';
2614
2615
        return $output;
2616
    }
2617
2618
    /**
2619
     * @param string $mode can be '%' or 'abs'
2620
     *                     otherwise this value will be used $this->progress_bar_mode
2621
     *
2622
     * @return string
2623
     */
2624
    public function getProgressBar($mode = null)
2625
    {
2626
        [$percentage, $text_add] = $this->get_progress_bar_text($mode);
2627
2628
        return self::get_progress_bar($percentage, $text_add);
2629
    }
2630
2631
    /**
2632
     * Gets the progress bar info to display inside the progress bar.
2633
     * Also used by scorm_api.php.
2634
     *
2635
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2636
     *                     we display a number of completed elements per total elements
2637
     * @param int    $add  Additional steps to fake as completed
2638
     *
2639
     * @return array Percentage or number and symbol (% or /xx)
2640
     */
2641
    public function get_progress_bar_text($mode = '', $add = 0)
2642
    {
2643
        if (empty($mode)) {
2644
            $mode = $this->progress_bar_mode;
2645
        }
2646
        $text = '';
2647
        $percentage = 0;
2648
        // If the option to use the score as progress is set for this learning
2649
        // path, then the rules are completely different: we assume only one
2650
        // item exists and the progress of the LP depends on the score
2651
        $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
2652
2653
        if ($scoreAsProgressSetting === true) {
2654
            $scoreAsProgress = $this->getUseScoreAsProgress();
2655
            if ($scoreAsProgress) {
2656
                // Get single item's score
2657
                $itemId = $this->get_current_item_id();
2658
                $item = $this->getItem($itemId);
2659
                $score = $item->get_score();
2660
                $maxScore = $item->get_max();
2661
                if ($mode = '%') {
2662
                    if (!empty($maxScore)) {
2663
                        $percentage = ((float) $score / (float) $maxScore) * 100;
2664
                    }
2665
                    $percentage = number_format($percentage, 0);
2666
                    $text = '%';
2667
                } else {
2668
                    $percentage = $score;
2669
                    $text = '/'.$maxScore;
2670
                }
2671
2672
                return [$percentage, $text];
2673
            }
2674
        }
2675
        // otherwise just continue the normal processing of progress
2676
        $total_items = $this->getTotalItemsCountWithoutDirs();
2677
        $completeItems = $this->get_complete_items_count();
2678
        if ($add != 0) {
2679
            $completeItems += $add;
2680
        }
2681
        if ($completeItems > $total_items) {
2682
            $completeItems = $total_items;
2683
        }
2684
2685
        if ($mode == '%') {
2686
            if ($total_items > 0) {
2687
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2688
            }
2689
            $percentage = number_format($percentage, 0);
2690
            $text = '%';
2691
        } elseif ($mode === 'abs') {
2692
            $percentage = $completeItems;
2693
            $text = '/'.$total_items;
2694
        }
2695
2696
        return [
2697
            $percentage,
2698
            $text,
2699
        ];
2700
    }
2701
2702
    /**
2703
     * Gets the progress bar mode.
2704
     *
2705
     * @return string The progress bar mode attribute
2706
     */
2707
    public function get_progress_bar_mode()
2708
    {
2709
        if (!empty($this->progress_bar_mode)) {
2710
            return $this->progress_bar_mode;
2711
        }
2712
2713
        return '%';
2714
    }
2715
2716
    /**
2717
     * Gets the learnpath theme (remote or local).
2718
     *
2719
     * @return string Learnpath theme
2720
     */
2721
    public function get_theme()
2722
    {
2723
        if (!empty($this->theme)) {
2724
            return $this->theme;
2725
        }
2726
2727
        return '';
2728
    }
2729
2730
    /**
2731
     * Gets the learnpath session id.
2732
     *
2733
     * @return int
2734
     */
2735
    public function get_lp_session_id()
2736
    {
2737
        if (!empty($this->lp_session_id)) {
2738
            return (int) $this->lp_session_id;
2739
        }
2740
2741
        return 0;
2742
    }
2743
2744
    /**
2745
     * Gets the learnpath image.
2746
     *
2747
     * @return string Web URL of the LP image
2748
     */
2749
    public function get_preview_image()
2750
    {
2751
        if (!empty($this->preview_image)) {
2752
            return $this->preview_image;
2753
        }
2754
2755
        return '';
2756
    }
2757
2758
    /**
2759
     * @param string $size
2760
     * @param string $path_type
2761
     *
2762
     * @return bool|string
2763
     */
2764
    public function get_preview_image_path($size = null, $path_type = 'web')
2765
    {
2766
        $preview_image = $this->get_preview_image();
2767
        if (isset($preview_image) && !empty($preview_image)) {
2768
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2769
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2770
2771
            if (isset($size)) {
2772
                $info = pathinfo($preview_image);
2773
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2774
2775
                if (file_exists($image_sys_path.$image_custom_size)) {
2776
                    if ($path_type == 'web') {
2777
                        return $image_path.$image_custom_size;
2778
                    } else {
2779
                        return $image_sys_path.$image_custom_size;
2780
                    }
2781
                }
2782
            } else {
2783
                if ($path_type == 'web') {
2784
                    return $image_path.$preview_image;
2785
                } else {
2786
                    return $image_sys_path.$preview_image;
2787
                }
2788
            }
2789
        }
2790
2791
        return false;
2792
    }
2793
2794
    /**
2795
     * Gets the learnpath author.
2796
     *
2797
     * @return string LP's author
2798
     */
2799
    public function get_author()
2800
    {
2801
        if (!empty($this->author)) {
2802
            return $this->author;
2803
        }
2804
2805
        return '';
2806
    }
2807
2808
    /**
2809
     * Gets hide table of contents.
2810
     *
2811
     * @return int
2812
     */
2813
    public function getHideTableOfContents()
2814
    {
2815
        return (int) $this->hide_toc_frame;
2816
    }
2817
2818
    /**
2819
     * Generate a new prerequisites string for a given item. If this item was a sco and
2820
     * its prerequisites were strings (instead of IDs), then transform those strings into
2821
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2822
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2823
     * same rule as the scormExport() method.
2824
     *
2825
     * @param int $item_id Item ID
2826
     *
2827
     * @return string Prerequisites string ready for the export as SCORM
2828
     */
2829
    public function get_scorm_prereq_string($item_id)
2830
    {
2831
        if ($this->debug > 0) {
2832
            error_log('In learnpath::get_scorm_prereq_string()');
2833
        }
2834
        if (!is_object($this->items[$item_id])) {
2835
            return false;
2836
        }
2837
        /** @var learnpathItem $oItem */
2838
        $oItem = $this->items[$item_id];
2839
        $prereq = $oItem->get_prereq_string();
2840
2841
        if (empty($prereq)) {
2842
            return '';
2843
        }
2844
        if (preg_match('/^\d+$/', $prereq) &&
2845
            isset($this->items[$prereq]) &&
2846
            is_object($this->items[$prereq])
2847
        ) {
2848
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2849
            // then simply return it (with the ITEM_ prefix).
2850
            //return 'ITEM_' . $prereq;
2851
            return $this->items[$prereq]->ref;
2852
        } else {
2853
            if (isset($this->refs_list[$prereq])) {
2854
                // It's a simple string item from which the ID can be found in the refs list,
2855
                // so we can transform it directly to an ID for export.
2856
                return $this->items[$this->refs_list[$prereq]]->ref;
2857
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2858
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2859
            } else {
2860
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2861
                // and replace them, one by one, by the internal IDs (chamilo db)
2862
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2863
                // by a space as well.
2864
                $find = [
2865
                    '&',
2866
                    '|',
2867
                    '~',
2868
                    '=',
2869
                    '<>',
2870
                    '{',
2871
                    '}',
2872
                    '*',
2873
                    '(',
2874
                    ')',
2875
                ];
2876
                $replace = [
2877
                    ' ',
2878
                    ' ',
2879
                    ' ',
2880
                    ' ',
2881
                    ' ',
2882
                    ' ',
2883
                    ' ',
2884
                    ' ',
2885
                    ' ',
2886
                    ' ',
2887
                ];
2888
                $prereq_mod = str_replace($find, $replace, $prereq);
2889
                $ids = explode(' ', $prereq_mod);
2890
                foreach ($ids as $id) {
2891
                    $id = trim($id);
2892
                    if (isset($this->refs_list[$id])) {
2893
                        $prereq = preg_replace(
2894
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2895
                            'ITEM_'.$this->refs_list[$id],
2896
                            $prereq
2897
                        );
2898
                    }
2899
                }
2900
2901
                return $prereq;
2902
            }
2903
        }
2904
    }
2905
2906
    /**
2907
     * Returns the XML DOM document's node.
2908
     *
2909
     * @param DOMNodeList $children Reference to a list of objects to search for the given ITEM_*
2910
     * @param string      $id       The identifier to look for
2911
     *
2912
     * @return mixed The reference to the element found with that identifier. False if not found
2913
     */
2914
    public function get_scorm_xml_node(DOMNodeList &$children, string $id, $nodeName = 'item', $attributeName = 'identifier')
2915
    {
2916
        for ($i = 0; $i < $children->length; $i++) {
2917
            $item_temp = $children->item($i);
2918
            if ($item_temp->nodeName == $nodeName) {
2919
                if ($item_temp instanceof DOMElement && $item_temp->getAttribute($attributeName) == $id) {
2920
                    return $item_temp;
2921
                }
2922
            }
2923
            $subchildren = $item_temp->childNodes;
2924
            if ($subchildren && $subchildren->length > 0) {
2925
                $val = $this->get_scorm_xml_node($subchildren, $id, $nodeName, $attributeName);
2926
                if (is_object($val)) {
2927
                    return $val;
2928
                }
2929
            }
2930
        }
2931
2932
        return false;
2933
    }
2934
2935
    /**
2936
     * Gets the status list for all LP's items.
2937
     *
2938
     * @return array Array of [index] => [item ID => current status]
2939
     */
2940
    public function get_items_status_list()
2941
    {
2942
        $list = [];
2943
        foreach ($this->ordered_items as $item_id) {
2944
            $list[] = [
2945
                $item_id => $this->items[$item_id]->get_status(),
2946
            ];
2947
        }
2948
2949
        return $list;
2950
    }
2951
2952
    /**
2953
     * Return the number of interactions for the given learnpath Item View ID.
2954
     * This method can be used as static.
2955
     *
2956
     * @param int $lp_iv_id  Item View ID
2957
     * @param int $course_id course id
2958
     *
2959
     * @return int
2960
     */
2961
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2962
    {
2963
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2964
        $lp_iv_id = (int) $lp_iv_id;
2965
        $course_id = (int) $course_id;
2966
2967
        $sql = "SELECT count(*) FROM $table
2968
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2969
        $res = Database::query($sql);
2970
        $num = 0;
2971
        if (Database::num_rows($res)) {
2972
            $row = Database::fetch_array($res);
2973
            $num = $row[0];
2974
        }
2975
2976
        return $num;
2977
    }
2978
2979
    /**
2980
     * Return the interactions as an array for the given lp_iv_id.
2981
     * This method can be used as static.
2982
     *
2983
     * @param int $lp_iv_id Learnpath Item View ID
2984
     *
2985
     * @return array
2986
     *
2987
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2988
     */
2989
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2990
    {
2991
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2992
        $list = [];
2993
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2994
        $lp_iv_id = (int) $lp_iv_id;
2995
2996
        if (empty($lp_iv_id) || empty($course_id)) {
2997
            return [];
2998
        }
2999
3000
        $sql = "SELECT * FROM $table
3001
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
3002
                ORDER BY order_id ASC";
3003
        $res = Database::query($sql);
3004
        $num = Database::num_rows($res);
3005
        if ($num > 0) {
3006
            $list[] = [
3007
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
3008
                'id' => api_htmlentities(get_lang('InteractionID'), ENT_QUOTES),
3009
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
3010
                'time' => api_htmlentities(get_lang('TimeFinished'), ENT_QUOTES),
3011
                'correct_responses' => api_htmlentities(get_lang('CorrectAnswers'), ENT_QUOTES),
3012
                'student_response' => api_htmlentities(get_lang('StudentResponse'), ENT_QUOTES),
3013
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
3014
                'latency' => api_htmlentities(get_lang('LatencyTimeSpent'), ENT_QUOTES),
3015
                'student_response_formatted' => '',
3016
            ];
3017
            while ($row = Database::fetch_array($res)) {
3018
                $studentResponseFormatted = urldecode($row['student_response']);
3019
                $content_student_response = explode('__|', $studentResponseFormatted);
3020
                if (count($content_student_response) > 0) {
3021
                    if (count($content_student_response) >= 3) {
3022
                        // Pop the element off the end of array.
3023
                        array_pop($content_student_response);
3024
                    }
3025
                    $studentResponseFormatted = implode(',', $content_student_response);
3026
                }
3027
3028
                $list[] = [
3029
                    'order_id' => $row['order_id'] + 1,
3030
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
3031
                    'type' => $row['interaction_type'],
3032
                    'time' => $row['completion_time'],
3033
                    'correct_responses' => '', // Hide correct responses from students.
3034
                    'student_response' => $row['student_response'],
3035
                    'result' => $row['result'],
3036
                    'latency' => $row['latency'],
3037
                    'student_response_formatted' => $studentResponseFormatted,
3038
                ];
3039
            }
3040
        }
3041
3042
        return $list;
3043
    }
3044
3045
    /**
3046
     * Return the number of objectives for the given learnpath Item View ID.
3047
     * This method can be used as static.
3048
     *
3049
     * @param int $lp_iv_id  Item View ID
3050
     * @param int $course_id Course ID
3051
     *
3052
     * @return int Number of objectives
3053
     */
3054
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
3055
    {
3056
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3057
        $course_id = (int) $course_id;
3058
        $lp_iv_id = (int) $lp_iv_id;
3059
        $sql = "SELECT count(*) FROM $table
3060
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
3061
        //@todo seems that this always returns 0
3062
        $res = Database::query($sql);
3063
        $num = 0;
3064
        if (Database::num_rows($res)) {
3065
            $row = Database::fetch_array($res);
3066
            $num = $row[0];
3067
        }
3068
3069
        return $num;
3070
    }
3071
3072
    /**
3073
     * Return the objectives as an array for the given lp_iv_id.
3074
     * This method can be used as static.
3075
     *
3076
     * @param int $lpItemViewId Learnpath Item View ID
3077
     * @param int $course_id
3078
     *
3079
     * @return array
3080
     *
3081
     * @todo    Translate labels
3082
     */
3083
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
3084
    {
3085
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
3086
        $lpItemViewId = (int) $lpItemViewId;
3087
3088
        if (empty($course_id) || empty($lpItemViewId)) {
3089
            return [];
3090
        }
3091
3092
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3093
        $sql = "SELECT * FROM $table
3094
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
3095
                ORDER BY order_id ASC";
3096
        $res = Database::query($sql);
3097
        $num = Database::num_rows($res);
3098
        $list = [];
3099
        if ($num > 0) {
3100
            $list[] = [
3101
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
3102
                'objective_id' => api_htmlentities(get_lang('ObjectiveID'), ENT_QUOTES),
3103
                'score_raw' => api_htmlentities(get_lang('ObjectiveRawScore'), ENT_QUOTES),
3104
                'score_max' => api_htmlentities(get_lang('ObjectiveMaxScore'), ENT_QUOTES),
3105
                'score_min' => api_htmlentities(get_lang('ObjectiveMinScore'), ENT_QUOTES),
3106
                'status' => api_htmlentities(get_lang('ObjectiveStatus'), ENT_QUOTES),
3107
            ];
3108
            while ($row = Database::fetch_array($res)) {
3109
                $list[] = [
3110
                    'order_id' => $row['order_id'] + 1,
3111
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
3112
                    'score_raw' => $row['score_raw'],
3113
                    'score_max' => $row['score_max'],
3114
                    'score_min' => $row['score_min'],
3115
                    'status' => $row['status'],
3116
                ];
3117
            }
3118
        }
3119
3120
        return $list;
3121
    }
3122
3123
    /**
3124
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
3125
     * used by get_html_toc() to be ready to display.
3126
     *
3127
     * @return array TOC as a table with 4 elements per row: title, link, status and level
3128
     */
3129
    public function get_toc()
3130
    {
3131
        $toc = [];
3132
        foreach ($this->ordered_items as $item_id) {
3133
            // TODO: Change this link generation and use new function instead.
3134
            $toc[] = [
3135
                'id' => $item_id,
3136
                'title' => $this->items[$item_id]->get_title(),
3137
                'status' => $this->items[$item_id]->get_status(),
3138
                'level' => $this->items[$item_id]->get_level(),
3139
                'type' => $this->items[$item_id]->get_type(),
3140
                'description' => $this->items[$item_id]->get_description(),
3141
                'path' => $this->items[$item_id]->get_path(),
3142
                'parent' => $this->items[$item_id]->get_parent(),
3143
            ];
3144
        }
3145
3146
        return $toc;
3147
    }
3148
3149
    /**
3150
     * Returns the CSS class name associated with a given item status.
3151
     *
3152
     * @param $status string an item status
3153
     *
3154
     * @return string CSS class name
3155
     */
3156
    public static function getStatusCSSClassName($status)
3157
    {
3158
        if (array_key_exists($status, self::STATUS_CSS_CLASS_NAME)) {
3159
            return self::STATUS_CSS_CLASS_NAME[$status];
3160
        }
3161
3162
        return '';
3163
    }
3164
3165
    /**
3166
     * Generate the tree of contents for this learnpath as an associative array tree
3167
     * with keys id, title, status, type, description, path, parent_id, children
3168
     * (title and descriptions as secured)
3169
     * and clues for CSS class composition:
3170
     *  - booleans is_current, is_parent_of_current, is_chapter
3171
     *  - string status_css_class_name.
3172
     *
3173
     * @param $parentId int restrict returned list to children of this parent
3174
     *
3175
     * @return array TOC as a table
3176
     */
3177
    public function getTOCTree($parentId = 0)
3178
    {
3179
        $toc = [];
3180
        $currentItemId = $this->get_current_item_id();
3181
3182
        foreach ($this->ordered_items as $itemId) {
3183
            $item = $this->items[$itemId];
3184
            if ($item->get_parent() == $parentId) {
3185
                $title = $item->get_title();
3186
                if (empty($title)) {
3187
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $itemId);
3188
                }
3189
3190
                $itemData = [
3191
                    'id' => $itemId,
3192
                    'title' => Security::remove_XSS($title),
3193
                    'status' => $item->get_status(),
3194
                    'level' => $item->get_level(), // FIXME should not be needed
3195
                    'type' => $item->get_type(),
3196
                    'description' => Security::remove_XSS($item->get_description()),
3197
                    'path' => $item->get_path(),
3198
                    'parent_id' => $item->get_parent(),
3199
                    'children' => $this->getTOCTree($itemId),
3200
                    'is_current' => ($itemId == $currentItemId),
3201
                    'is_parent_of_current' => false,
3202
                    'is_chapter' => in_array($item->get_type(), self::getChapterTypes()),
3203
                    'status_css_class_name' => $this->getStatusCSSClassName($item->get_status()),
3204
                    'current_id' => $currentItemId, // FIXME should not be needed, not a property of item
3205
                ];
3206
3207
                if (!empty($itemData['children'])) {
3208
                    foreach ($itemData['children'] as $child) {
3209
                        if ($child['is_current'] || $child['is_parent_of_current']) {
3210
                            $itemData['is_parent_of_current'] = true;
3211
                            break;
3212
                        }
3213
                    }
3214
                }
3215
3216
                $toc[] = $itemData;
3217
            }
3218
        }
3219
3220
        return $toc;
3221
    }
3222
3223
    /**
3224
     * Generate and return the table of contents for this learnpath. The JS
3225
     * table returned is used inside of scorm_api.php.
3226
     *
3227
     * @param string $varname
3228
     *
3229
     * @return string A JS array variable construction
3230
     */
3231
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
3232
    {
3233
        $toc = $varname.' = new Array();';
3234
        foreach ($this->ordered_items as $item_id) {
3235
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
3236
        }
3237
3238
        return $toc;
3239
    }
3240
3241
    /**
3242
     * Gets the learning path type.
3243
     *
3244
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
3245
     *
3246
     * @return mixed Type ID or name, depending on the parameter
3247
     */
3248
    public function get_type($get_name = false)
3249
    {
3250
        $res = false;
3251
        if (!empty($this->type) && (!$get_name)) {
3252
            $res = $this->type;
3253
        }
3254
3255
        return $res;
3256
    }
3257
3258
    /**
3259
     * Gets the learning path type as static method.
3260
     *
3261
     * @param int $lp_id
3262
     *
3263
     * @return mixed Returns the lp_type: 1 = Chamilo lms / 2 = SCORM
3264
     */
3265
    public static function get_type_static($lp_id = 0)
3266
    {
3267
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3268
        $lp_id = (int) $lp_id;
3269
        $sql = "SELECT lp_type FROM $tbl_lp
3270
                WHERE iid = $lp_id";
3271
        $res = Database::query($sql);
3272
        if ($res === false) {
3273
            return null;
3274
        }
3275
        if (Database::num_rows($res) <= 0) {
3276
            return null;
3277
        }
3278
        $row = Database::fetch_array($res);
3279
3280
        return $row['lp_type'];
3281
    }
3282
3283
    /**
3284
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3285
     * This method can be used as abstract and is recursive.
3286
     *
3287
     * @param int $lp        Learnpath ID
3288
     * @param int $parent    Parent ID of the items to look for
3289
     * @param int $course_id
3290
     *
3291
     * @return array Ordered list of item IDs (empty array on error)
3292
     */
3293
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3294
    {
3295
        if (empty($course_id)) {
3296
            $course_id = api_get_course_int_id();
3297
        } else {
3298
            $course_id = (int) $course_id;
3299
        }
3300
        $list = [];
3301
3302
        if (empty($lp)) {
3303
            return $list;
3304
        }
3305
3306
        $lp = (int) $lp;
3307
        $parent = (int) $parent;
3308
3309
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3310
        $sql = "SELECT iid FROM $tbl_lp_item
3311
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3312
                ORDER BY display_order";
3313
3314
        $res = Database::query($sql);
3315
        while ($row = Database::fetch_array($res)) {
3316
            $sublist = self::get_flat_ordered_items_list(
3317
                $lp,
3318
                $row['iid'],
3319
                $course_id
3320
            );
3321
            $list[] = $row['iid'];
3322
            foreach ($sublist as $item) {
3323
                $list[] = $item;
3324
            }
3325
        }
3326
3327
        return $list;
3328
    }
3329
3330
    /**
3331
     * @return array
3332
     */
3333
    public static function getChapterTypes()
3334
    {
3335
        return [
3336
            'dir',
3337
        ];
3338
    }
3339
3340
    /**
3341
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3342
     *
3343
     * @param $tree
3344
     *
3345
     * @return array HTML TOC ready to display
3346
     */
3347
    public function getParentToc($tree)
3348
    {
3349
        if (empty($tree)) {
3350
            $tree = $this->get_toc();
3351
        }
3352
        $dirTypes = self::getChapterTypes();
3353
        $myCurrentId = $this->get_current_item_id();
3354
        $listParent = [];
3355
        $listChildren = [];
3356
        $listNotParent = [];
3357
        $list = [];
3358
        foreach ($tree as $subtree) {
3359
            if (in_array($subtree['type'], $dirTypes)) {
3360
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3361
                $subtree['children'] = $listChildren;
3362
                if (!empty($subtree['children'])) {
3363
                    foreach ($subtree['children'] as $subItem) {
3364
                        if ($subItem['id'] == $this->current) {
3365
                            $subtree['parent_current'] = 'in';
3366
                            $subtree['current'] = 'on';
3367
                        }
3368
                    }
3369
                }
3370
                $listParent[] = $subtree;
3371
            }
3372
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
3373
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3374
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3375
                }
3376
3377
                $title = Security::remove_XSS($subtree['title']);
3378
                unset($subtree['title']);
3379
3380
                if (empty($title)) {
3381
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3382
                }
3383
                $classStyle = null;
3384
                if ($subtree['id'] == $this->current) {
3385
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3386
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3387
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3388
                }
3389
                $subtree['title'] = $title;
3390
                $subtree['class'] = $classStyle.' '.$cssStatus;
3391
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3392
                $subtree['current_id'] = $myCurrentId;
3393
                $listNotParent[] = $subtree;
3394
            }
3395
        }
3396
3397
        $list['are_parents'] = $listParent;
3398
        $list['not_parents'] = $listNotParent;
3399
3400
        return $list;
3401
    }
3402
3403
    /**
3404
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3405
     *
3406
     * @param array $tree
3407
     * @param int   $id
3408
     * @param bool  $parent
3409
     *
3410
     * @return array HTML TOC ready to display
3411
     */
3412
    public function getChildrenToc($tree, $id, $parent = true)
3413
    {
3414
        if (empty($tree)) {
3415
            $tree = $this->get_toc();
3416
        }
3417
3418
        $dirTypes = self::getChapterTypes();
3419
        $currentItemId = $this->get_current_item_id();
3420
        $list = [];
3421
3422
        foreach ($tree as $subtree) {
3423
            $subtree['tree'] = null;
3424
3425
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3426
                if ($subtree['id'] == $this->current) {
3427
                    $subtree['current'] = 'active';
3428
                } else {
3429
                    $subtree['current'] = null;
3430
                }
3431
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3432
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3433
                }
3434
3435
                $title = Security::remove_XSS($subtree['title']);
3436
                unset($subtree['title']);
3437
                if (empty($title)) {
3438
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3439
                }
3440
3441
                $classStyle = null;
3442
                if ($subtree['id'] == $this->current) {
3443
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3444
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3445
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3446
                }
3447
3448
                if (in_array($subtree['type'], $dirTypes)) {
3449
                    $subtree['title'] = stripslashes($title);
3450
                } else {
3451
                    $subtree['title'] = $title;
3452
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3453
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3454
                    $subtree['current_id'] = $currentItemId;
3455
                }
3456
                $list[] = $subtree;
3457
            }
3458
        }
3459
3460
        return $list;
3461
    }
3462
3463
    /**
3464
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3465
     *
3466
     * @param array $toc_list
3467
     *
3468
     * @return array HTML TOC ready to display
3469
     */
3470
    public function getListArrayToc($toc_list = [])
3471
    {
3472
        if (empty($toc_list)) {
3473
            $toc_list = $this->get_toc();
3474
        }
3475
        // Temporary variables.
3476
        $currentItemId = $this->get_current_item_id();
3477
        $list = [];
3478
        $arrayList = [];
3479
3480
        foreach ($toc_list as $item) {
3481
            $list['id'] = $item['id'];
3482
            $list['status'] = $item['status'];
3483
            $cssStatus = null;
3484
3485
            if (array_key_exists($item['status'], self::STATUS_CSS_CLASS_NAME)) {
3486
                $cssStatus = self::STATUS_CSS_CLASS_NAME[$item['status']];
3487
            }
3488
3489
            $classStyle = ' ';
3490
            $dirTypes = self::getChapterTypes();
3491
3492
            if (in_array($item['type'], $dirTypes)) {
3493
                $classStyle = 'scorm_item_section ';
3494
            }
3495
            if ($item['id'] == $this->current) {
3496
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3497
            } elseif (!in_array($item['type'], $dirTypes)) {
3498
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3499
            }
3500
            $title = $item['title'];
3501
            if (empty($title)) {
3502
                $title = self::rl_get_resource_name(
3503
                    api_get_course_id(),
3504
                    $this->get_id(),
3505
                    $item['id']
3506
                );
3507
            }
3508
            $title = Security::remove_XSS($item['title']);
3509
3510
            if (empty($item['description'])) {
3511
                $list['description'] = $title;
3512
            } else {
3513
                $list['description'] = $item['description'];
3514
            }
3515
3516
            $list['class'] = $classStyle.' '.$cssStatus;
3517
            $list['level'] = $item['level'];
3518
            $list['type'] = $item['type'];
3519
3520
            if (in_array($item['type'], $dirTypes)) {
3521
                $list['css_level'] = 'level_'.$item['level'];
3522
            } else {
3523
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3524
            }
3525
3526
            if (in_array($item['type'], $dirTypes)) {
3527
                $list['title'] = stripslashes($title);
3528
            } else {
3529
                $list['title'] = stripslashes($title);
3530
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3531
                $list['current_id'] = $currentItemId;
3532
            }
3533
            $arrayList[] = $list;
3534
        }
3535
3536
        return $arrayList;
3537
    }
3538
3539
    /**
3540
     * Returns an HTML-formatted string ready to display with teacher buttons
3541
     * in LP view menu.
3542
     *
3543
     * @return string HTML TOC ready to display
3544
     */
3545
    public function get_teacher_toc_buttons()
3546
    {
3547
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3548
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3549
        $html = '';
3550
        if ($isAllow && $hideIcons == false) {
3551
            if ($this->get_lp_session_id() == api_get_session_id()) {
3552
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3553
                $html .= '<div class="btn-group">';
3554
                $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'>".
3555
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3556
                $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'>".
3557
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3558
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3559
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3560
                $html .= '</div>';
3561
                $html .= '</div>';
3562
            }
3563
        }
3564
3565
        return $html;
3566
    }
3567
3568
    /**
3569
     * Returns an HTML-formatted string ready to display flow buttons
3570
     * in LP view menu.
3571
     *
3572
     * @return string HTML TOC ready to display
3573
     */
3574
    public function getFlowLpbuttons()
3575
    {
3576
        $allowFlowButtons = api_get_configuration_value('lp_enable_flow');
3577
        $html = '';
3578
        if ($allowFlowButtons) {
3579
            $nextLpId = self::getFlowNextLpId($this->lp_id, api_get_course_int_id());
3580
            $prevLpId = self::getFlowPrevLpId($this->lp_id, api_get_course_int_id());
3581
            if (!empty($nextLpId) || !empty($prevLpId)) {
3582
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3583
                $html .= '<div class="btn-group">';
3584
                if ($prevLpId > 0) {
3585
                    $html .= "<a class='btn btn-sm btn-default' href='lp_controller.php?".api_get_cidreq()."&action=view&lp_id=".$prevLpId."' target='_parent'>".
3586
                        Display::returnFontAwesomeIcon('arrow-left').'&nbsp;'.get_lang('Previous')."</a>";
3587
                }
3588
                if ($nextLpId > 0) {
3589
                    $html .= "<a class='btn btn-sm btn-default' href='lp_controller.php?".api_get_cidreq()."&action=view&lp_id=".$nextLpId."' target='_parent'>".
3590
                        get_lang('Next').'&nbsp;'.Display::returnFontAwesomeIcon('arrow-right')."</a>";
3591
                }
3592
                $html .= '</div>';
3593
                $html .= '</div>';
3594
            }
3595
        }
3596
3597
        return $html;
3598
    }
3599
3600
    /**
3601
     * Gets the learnpath maker name - generally the editor's name.
3602
     *
3603
     * @return string Learnpath maker name
3604
     */
3605
    public function get_maker()
3606
    {
3607
        if (!empty($this->maker)) {
3608
            return $this->maker;
3609
        }
3610
3611
        return '';
3612
    }
3613
3614
    /**
3615
     * Gets the learnpath name/title.
3616
     *
3617
     * @return string Learnpath name/title
3618
     */
3619
    public function get_name()
3620
    {
3621
        if (!empty($this->name)) {
3622
            return $this->name;
3623
        }
3624
3625
        return 'N/A';
3626
    }
3627
3628
    /**
3629
     * @return string
3630
     */
3631
    public function getNameNoTags()
3632
    {
3633
        return Security::remove_XSS(strip_tags($this->get_name()));
3634
    }
3635
3636
    /**
3637
     * Gets a link to the resource from the present location, depending on item ID.
3638
     *
3639
     * @param string $type         Type of link expected
3640
     * @param int    $item_id      Learnpath item ID
3641
     * @param bool   $provided_toc
3642
     *
3643
     * @return string $provided_toc Link to the lp_item resource
3644
     */
3645
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
3646
    {
3647
        $course_id = $this->get_course_int_id();
3648
        $item_id = (int) $item_id;
3649
3650
        if (empty($item_id)) {
3651
            $item_id = $this->get_current_item_id();
3652
3653
            if (empty($item_id)) {
3654
                //still empty, this means there was no item_id given and we are not in an object context or
3655
                //the object property is empty, return empty link
3656
                $this->first();
3657
3658
                return '';
3659
            }
3660
        }
3661
3662
        $file = '';
3663
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3664
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3665
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3666
3667
        $sql = "SELECT
3668
                    l.lp_type as ltype,
3669
                    l.path as lpath,
3670
                    li.item_type as litype,
3671
                    li.path as lipath,
3672
                    li.parameters as liparams
3673
        		FROM $lp_table l
3674
                INNER JOIN $lp_item_table li
3675
                ON (li.lp_id = l.iid)
3676
        		WHERE
3677
        		    li.iid = $item_id
3678
        		";
3679
        $res = Database::query($sql);
3680
        if (Database::num_rows($res) > 0) {
3681
            $row = Database::fetch_array($res);
3682
            $lp_type = $row['ltype'];
3683
            $lp_path = $row['lpath'];
3684
            $lp_item_type = $row['litype'];
3685
            $lp_item_path = $row['lipath'];
3686
            $lp_item_params = $row['liparams'];
3687
3688
            if (empty($lp_item_params) && strpos($lp_item_path, '?') !== false) {
3689
                [$lp_item_path, $lp_item_params] = explode('?', $lp_item_path);
3690
            }
3691
            $sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3692
            if ($type === 'http') {
3693
                //web path
3694
                $course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3695
            } else {
3696
                $course_path = $sys_course_path; //system path
3697
            }
3698
3699
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3700
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3701
            if (in_array(
3702
                $lp_item_type,
3703
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication', 'xapi', 'h5p', 'survey']
3704
            )
3705
            ) {
3706
                $lp_type = 1;
3707
            }
3708
3709
            // Now go through the specific cases to get the end of the path
3710
            // @todo Use constants instead of int values.
3711
            switch ($lp_type) {
3712
                case 1:
3713
                    $file = self::rl_get_resource_link_for_learnpath(
3714
                        $course_id,
3715
                        $this->get_id(),
3716
                        $item_id,
3717
                        $this->get_view_id(),
3718
                        $this->get_lp_session_id()
3719
                    );
3720
                    switch ($lp_item_type) {
3721
                        case 'document':
3722
                            // Shows a button to download the file instead of just downloading the file directly.
3723
                            $documentPathInfo = pathinfo($file);
3724
                            if (isset($documentPathInfo['extension'])) {
3725
                                $parsed = parse_url($documentPathInfo['extension']);
3726
                                if (isset($parsed['path'])) {
3727
                                    $extension = $parsed['path'];
3728
                                    $extensionsToDownload = [
3729
                                        'zip',
3730
                                        'ppt',
3731
                                        'pptx',
3732
                                        'odp',
3733
                                        'xls',
3734
                                        'xlsx',
3735
                                        'ods',
3736
                                        'csv',
3737
                                        'doc',
3738
                                        'docx',
3739
                                        'odt',
3740
                                        'dot',
3741
                                    ];
3742
3743
                                    $onlyofficeEditable = false;
3744
3745
                                    if (OnlyofficePlugin::create()->isEnabled()) {
3746
                                        $lpItem = $this->getItem($item_id);
3747
3748
                                        if ($lpItem->get_type() == 'document'
3749
                                            && OnlyofficePlugin::isExtensionAllowed($extension)
3750
                                        ) {
3751
                                            $docId = $lpItem->get_path();
3752
3753
                                            if (method_exists('OnlyofficeTools', 'getPathToView')) {
3754
                                                $pathToView = OnlyofficeTools::getPathToView($docId, false);
3755
                                                // getPathView returns empty on error, so if this is the case,
3756
                                                // fallback to normal viewer/downloader
3757
                                                if (!empty($pathToView)) {
3758
                                                    $file = $pathToView;
3759
                                                    $onlyofficeEditable = true;
3760
                                                }
3761
                                            }
3762
                                        }
3763
                                    }
3764
3765
                                    if (in_array($extension, $extensionsToDownload) && false === $onlyofficeEditable) {
3766
                                        $file = api_get_path(WEB_CODE_PATH)
3767
                                            .'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'
3768
                                            .api_get_cidreq();
3769
                                    }
3770
                                }
3771
                            }
3772
                            break;
3773
                        case 'dir':
3774
                            $file = 'lp_content.php?type=dir&'.api_get_cidreq();
3775
                            break;
3776
                        case 'link':
3777
                            if (!empty($file)) {
3778
                                if (Link::is_youtube_link($file)) {
3779
                                    $src = Link::get_youtube_video_id($file);
3780
                                    $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3781
                                } elseif (Link::isVimeoLink($file)) {
3782
                                    $src = Link::getVimeoLinkId($file);
3783
                                    $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3784
                                } else {
3785
                                    // If the current site is HTTPS and the link is
3786
                                    // HTTP, browsers will refuse opening the link
3787
                                    $urlId = api_get_current_access_url_id();
3788
                                    $url = api_get_access_url($urlId, false);
3789
                                    $protocol = substr($url['url'], 0, 5);
3790
                                    if ($protocol === 'https') {
3791
                                        $linkProtocol = substr($file, 0, 5);
3792
                                        if ($linkProtocol === 'http:') {
3793
                                            //this is the special intervention case
3794
                                            $file = api_get_path(
3795
                                                    WEB_CODE_PATH
3796
                                                ).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3797
                                        }
3798
                                    }
3799
                                }
3800
                            }
3801
                            break;
3802
                        case 'quiz':
3803
                            // Check how much attempts of a exercise exits in lp
3804
                            $lp_item_id = $this->get_current_item_id();
3805
                            $lp_view_id = $this->get_view_id();
3806
3807
                            $prevent_reinit = null;
3808
                            if (isset($this->items[$this->current])) {
3809
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3810
                            }
3811
3812
                            if (empty($provided_toc)) {
3813
                                if ($this->debug > 0) {
3814
                                    error_log('In learnpath::get_link() Loading get_toc ', 0);
3815
                                }
3816
                                $list = $this->get_toc();
3817
                            } else {
3818
                                if ($this->debug > 0) {
3819
                                    error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
3820
                                }
3821
                                $list = $provided_toc;
3822
                            }
3823
3824
                            $type_quiz = false;
3825
                            foreach ($list as $toc) {
3826
                                if ($toc['id'] == $lp_item_id && $toc['type'] === 'quiz') {
3827
                                    $type_quiz = true;
3828
                                }
3829
                            }
3830
3831
                            if ($type_quiz) {
3832
                                $lp_item_id = (int) $lp_item_id;
3833
                                $lp_view_id = (int) $lp_view_id;
3834
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3835
                                        WHERE
3836
                                            c_id = $course_id AND
3837
                                            lp_item_id='".$lp_item_id."' AND
3838
                                            lp_view_id ='".$lp_view_id."' AND
3839
                                            status='completed'";
3840
                                $result = Database::query($sql);
3841
                                $row_count = Database::fetch_row($result);
3842
                                $count_item_view = (int) $row_count[0];
3843
                                $not_multiple_attempt = 0;
3844
                                if ($prevent_reinit === 1 && $count_item_view > 0) {
3845
                                    $not_multiple_attempt = 1;
3846
                                }
3847
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3848
                            }
3849
                            break;
3850
                    }
3851
3852
                    $tmp_array = explode('/', $file);
3853
                    $document_name = $tmp_array[count($tmp_array) - 1];
3854
                    if (strpos($document_name, '_DELETED_')) {
3855
                        $file = 'blank.php?error=document_deleted';
3856
                    }
3857
                    break;
3858
                case 2:
3859
                    if ($this->debug > 2) {
3860
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3861
                    }
3862
3863
                    if ($lp_item_type != 'dir') {
3864
                        // Quite complex here:
3865
                        // We want to make sure 'http://' (and similar) links can
3866
                        // be loaded as is (withouth the Chamilo path in front) but
3867
                        // some contents use this form: resource.htm?resource=http://blablabla
3868
                        // which means we have to find a protocol at the path's start, otherwise
3869
                        // it should not be considered as an external URL.
3870
                        // if ($this->prerequisites_match($item_id)) {
3871
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3872
                            if ($this->debug > 2) {
3873
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3874
                            }
3875
                            // Distant url, return as is.
3876
                            $file = $lp_item_path;
3877
                        } else {
3878
                            if ($this->debug > 2) {
3879
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3880
                            }
3881
                            // Prevent getting untranslatable urls.
3882
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3883
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3884
                            // Prepare the path.
3885
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3886
                            // TODO: Fix this for urls with protocol header.
3887
                            $file = str_replace('//', '/', $file);
3888
                            $file = str_replace(':/', '://', $file);
3889
                            if (substr($lp_path, -1) == '/') {
3890
                                $lp_path = substr($lp_path, 0, -1);
3891
                            }
3892
3893
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3894
                                // if file not found.
3895
                                $decoded = html_entity_decode($lp_item_path);
3896
                                [$decoded] = explode('?', $decoded);
3897
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3898
                                    $file = self::rl_get_resource_link_for_learnpath(
3899
                                        $course_id,
3900
                                        $this->get_id(),
3901
                                        $item_id,
3902
                                        $this->get_view_id()
3903
                                    );
3904
                                    if (empty($file)) {
3905
                                        $file = 'blank.php?error=document_not_found';
3906
                                    } else {
3907
                                        $tmp_array = explode('/', $file);
3908
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3909
                                        if (strpos($document_name, '_DELETED_')) {
3910
                                            $file = 'blank.php?error=document_deleted';
3911
                                        } else {
3912
                                            $file = 'blank.php?error=document_not_found';
3913
                                        }
3914
                                    }
3915
                                } else {
3916
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3917
                                }
3918
                            }
3919
                        }
3920
3921
                        // We want to use parameters if they were defined in the imsmanifest
3922
                        if (strpos($file, 'blank.php') === false) {
3923
                            $lp_item_params = ltrim($lp_item_params, '?');
3924
                            $file .= (strstr($file, '?') === false ? '?' : '').$lp_item_params;
3925
                        }
3926
                    } else {
3927
                        $file = 'lp_content.php?type=dir&'.api_get_cidreq();
3928
                    }
3929
                    break;
3930
                case 3:
3931
                    if ($this->debug > 2) {
3932
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3933
                    }
3934
                    // Formatting AICC HACP append URL.
3935
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3936
                    if (!empty($lp_item_params)) {
3937
                        $aicc_append .= $lp_item_params.'&';
3938
                    }
3939
                    if ($lp_item_type != 'dir') {
3940
                        // Quite complex here:
3941
                        // We want to make sure 'http://' (and similar) links can
3942
                        // be loaded as is (withouth the Chamilo path in front) but
3943
                        // some contents use this form: resource.htm?resource=http://blablabla
3944
                        // which means we have to find a protocol at the path's start, otherwise
3945
                        // it should not be considered as an external URL.
3946
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3947
                            if ($this->debug > 2) {
3948
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3949
                            }
3950
                            // Distant url, return as is.
3951
                            $file = $lp_item_path;
3952
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3953
                            /*
3954
                            if (stristr($file,'<servername>') !== false) {
3955
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3956
                            }
3957
                            */
3958
                            if (stripos($file, '<servername>') !== false) {
3959
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3960
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3961
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3962
                            }
3963
3964
                            $file .= $aicc_append;
3965
                        } else {
3966
                            if ($this->debug > 2) {
3967
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3968
                            }
3969
                            // Prevent getting untranslatable urls.
3970
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3971
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3972
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3973
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3974
                            // TODO: Fix this for urls with protocol header.
3975
                            $file = str_replace('//', '/', $file);
3976
                            $file = str_replace(':/', '://', $file);
3977
                            $file .= $aicc_append;
3978
                        }
3979
                    } else {
3980
                        $file = 'lp_content.php?type=dir&'.api_get_cidreq();
3981
                    }
3982
                    break;
3983
                case 4:
3984
                    break;
3985
                default:
3986
                    break;
3987
            }
3988
            // Replace &amp; by & because &amp; will break URL with params
3989
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3990
        }
3991
        if ($this->debug > 2) {
3992
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3993
        }
3994
3995
        return $file;
3996
    }
3997
3998
    /**
3999
     * Gets the latest usable view or generate a new one.
4000
     *
4001
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
4002
     * @param int $userId      The user ID, as $this->get_user_id() is not always available
4003
     *
4004
     * @return int DB lp_view id
4005
     */
4006
    public function get_view($attempt_num = 0, $userId = null)
4007
    {
4008
        $search = '';
4009
        // Use $attempt_num to enable multi-views management (disabled so far).
4010
        if ($attempt_num != 0 && intval(strval($attempt_num)) == $attempt_num) {
4011
            $search = 'AND view_count = '.$attempt_num;
4012
        }
4013
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
4014
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4015
4016
        $course_id = api_get_course_int_id();
4017
        $sessionId = api_get_session_id();
4018
4019
        // Check user ID.
4020
        if (empty($userId)) {
4021
            if (empty($this->get_user_id())) {
4022
                $this->error = 'User ID is empty in learnpath::get_view()';
4023
4024
                return null;
4025
            } else {
4026
                $userId = $this->get_user_id();
4027
            }
4028
        }
4029
4030
        $sql = "SELECT iid, view_count FROM $lp_view_table
4031
        		WHERE
4032
        		    c_id = $course_id AND
4033
        		    lp_id = ".$this->get_id()." AND
4034
        		    user_id = ".$userId." AND
4035
        		    session_id = $sessionId
4036
        		    $search
4037
                ORDER BY view_count DESC";
4038
        $res = Database::query($sql);
4039
        if (Database::num_rows($res) > 0) {
4040
            $row = Database::fetch_array($res);
4041
            $this->lp_view_id = $row['iid'];
4042
        } elseif (!api_is_invitee()) {
4043
            // There is no database record, create one.
4044
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
4045
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
4046
            Database::query($sql);
4047
            $id = Database::insert_id();
4048
            $this->lp_view_id = $id;
4049
4050
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
4051
            Database::query($sql);
4052
        }
4053
4054
        return $this->lp_view_id;
4055
    }
4056
4057
    /**
4058
     * Gets the current view id.
4059
     *
4060
     * @return int View ID (from lp_view)
4061
     */
4062
    public function get_view_id()
4063
    {
4064
        if (!empty($this->lp_view_id)) {
4065
            return (int) $this->lp_view_id;
4066
        }
4067
4068
        return 0;
4069
    }
4070
4071
    /**
4072
     * Gets the update queue.
4073
     *
4074
     * @return array Array containing IDs of items to be updated by JavaScript
4075
     */
4076
    public function get_update_queue()
4077
    {
4078
        return $this->update_queue;
4079
    }
4080
4081
    /**
4082
     * Gets the user ID.
4083
     *
4084
     * @return int User ID
4085
     */
4086
    public function get_user_id()
4087
    {
4088
        if (!empty($this->user_id)) {
4089
            return (int) $this->user_id;
4090
        }
4091
4092
        return false;
4093
    }
4094
4095
    /**
4096
     * Checks if any of the items has an audio element attached.
4097
     *
4098
     * @return bool True or false
4099
     */
4100
    public function has_audio()
4101
    {
4102
        $has = false;
4103
        foreach ($this->items as $i => $item) {
4104
            if (!empty($this->items[$i]->audio)) {
4105
                $has = true;
4106
                break;
4107
            }
4108
        }
4109
4110
        return $has;
4111
    }
4112
4113
    /**
4114
     * Moves an item up and down at its level.
4115
     *
4116
     * @param int    $id        Item to move up and down
4117
     * @param string $direction Direction 'up' or 'down'
4118
     *
4119
     * @return bool|int
4120
     */
4121
    public function move_item($id, $direction)
4122
    {
4123
        $course_id = api_get_course_int_id();
4124
        if (empty($id) || empty($direction)) {
4125
            return false;
4126
        }
4127
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
4128
        $sql_sel = "SELECT *
4129
                    FROM $tbl_lp_item
4130
                    WHERE
4131
                        iid = $id
4132
                    ";
4133
        $res_sel = Database::query($sql_sel);
4134
        // Check if elem exists.
4135
        if (Database::num_rows($res_sel) < 1) {
4136
            return false;
4137
        }
4138
        // Gather data.
4139
        $row = Database::fetch_array($res_sel);
4140
        $previous = $row['previous_item_id'];
4141
        $next = $row['next_item_id'];
4142
        $display = $row['display_order'];
4143
        $parent = $row['parent_item_id'];
4144
        $lp = $row['lp_id'];
4145
        // Update the item (switch with previous/next one).
4146
        switch ($direction) {
4147
            case 'up':
4148
                if ($display > 1) {
4149
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4150
                                 WHERE iid = $previous";
4151
                    $res_sel2 = Database::query($sql_sel2);
4152
                    if (Database::num_rows($res_sel2) < 1) {
4153
                        $previous_previous = 0;
4154
                    }
4155
                    // Gather data.
4156
                    $row2 = Database::fetch_array($res_sel2);
4157
                    $previous_previous = $row2['previous_item_id'];
4158
                    // Update previous_previous item (switch "next" with current).
4159
                    if ($previous_previous != 0) {
4160
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4161
                                        next_item_id = $id
4162
                                    WHERE iid = $previous_previous";
4163
                        Database::query($sql_upd2);
4164
                    }
4165
                    // Update previous item (switch with current).
4166
                    if ($previous != 0) {
4167
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4168
                                    next_item_id = $next,
4169
                                    previous_item_id = $id,
4170
                                    display_order = display_order +1
4171
                                    WHERE iid = $previous";
4172
                        Database::query($sql_upd2);
4173
                    }
4174
4175
                    // Update current item (switch with previous).
4176
                    if ($id != 0) {
4177
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4178
                                        next_item_id = $previous,
4179
                                        previous_item_id = $previous_previous,
4180
                                        display_order = display_order-1
4181
                                    WHERE c_id = ".$course_id." AND id = $id";
4182
                        Database::query($sql_upd2);
4183
                    }
4184
                    // Update next item (new previous item).
4185
                    if (!empty($next)) {
4186
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
4187
                                     WHERE iid = $next";
4188
                        Database::query($sql_upd2);
4189
                    }
4190
                    $display = $display - 1;
4191
                }
4192
                break;
4193
            case 'down':
4194
                if ($next != 0) {
4195
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4196
                                 WHERE iid = $next";
4197
                    $res_sel2 = Database::query($sql_sel2);
4198
                    if (Database::num_rows($res_sel2) < 1) {
4199
                        $next_next = 0;
4200
                    }
4201
                    // Gather data.
4202
                    $row2 = Database::fetch_array($res_sel2);
4203
                    $next_next = $row2['next_item_id'];
4204
                    // Update previous item (switch with current).
4205
                    if ($previous != 0) {
4206
                        $sql_upd2 = "UPDATE $tbl_lp_item
4207
                                     SET next_item_id = $next
4208
                                     WHERE iid = $previous";
4209
                        Database::query($sql_upd2);
4210
                    }
4211
                    // Update current item (switch with previous).
4212
                    if ($id != 0) {
4213
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4214
                                     previous_item_id = $next,
4215
                                     next_item_id = $next_next,
4216
                                     display_order = display_order + 1
4217
                                     WHERE iid = $id";
4218
                        Database::query($sql_upd2);
4219
                    }
4220
4221
                    // Update next item (new previous item).
4222
                    if ($next != 0) {
4223
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4224
                                     previous_item_id = $previous,
4225
                                     next_item_id = $id,
4226
                                     display_order = display_order-1
4227
                                     WHERE iid = $next";
4228
                        Database::query($sql_upd2);
4229
                    }
4230
4231
                    // Update next_next item (switch "previous" with current).
4232
                    if ($next_next != 0) {
4233
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4234
                                     previous_item_id = $id
4235
                                     WHERE iid = $next_next";
4236
                        Database::query($sql_upd2);
4237
                    }
4238
                    $display = $display + 1;
4239
                }
4240
                break;
4241
            default:
4242
                return false;
4243
        }
4244
4245
        return $display;
4246
    }
4247
4248
    /**
4249
     * Move a LP up (display_order).
4250
     *
4251
     * @param int $lp_id      Learnpath ID
4252
     * @param int $categoryId Category ID
4253
     *
4254
     * @return bool
4255
     */
4256
    public static function move_up($lp_id, $categoryId = 0)
4257
    {
4258
        $courseId = api_get_course_int_id();
4259
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4260
4261
        $categoryCondition = '';
4262
        if (!empty($categoryId)) {
4263
            $categoryId = (int) $categoryId;
4264
            $categoryCondition = " AND category_id = $categoryId";
4265
        }
4266
        $sql = "SELECT * FROM $lp_table
4267
                WHERE c_id = $courseId
4268
                $categoryCondition
4269
                ORDER BY display_order";
4270
        $res = Database::query($sql);
4271
        if ($res === false) {
4272
            return false;
4273
        }
4274
4275
        $lps = [];
4276
        $lp_order = [];
4277
        $num = Database::num_rows($res);
4278
        // First check the order is correct, globally (might be wrong because
4279
        // of versions < 1.8.4)
4280
        if ($num > 0) {
4281
            $i = 1;
4282
            while ($row = Database::fetch_array($res)) {
4283
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
4284
                    $sql = "UPDATE $lp_table SET display_order = $i
4285
                            WHERE iid = ".$row['iid'];
4286
                    Database::query($sql);
4287
                }
4288
                $row['display_order'] = $i;
4289
                $lps[$row['iid']] = $row;
4290
                $lp_order[$i] = $row['iid'];
4291
                $i++;
4292
            }
4293
        }
4294
        if ($num > 1) { // If there's only one element, no need to sort.
4295
            $order = $lps[$lp_id]['display_order'];
4296
            if ($order > 1) { // If it's the first element, no need to move up.
4297
                $sql = "UPDATE $lp_table SET display_order = $order
4298
                        WHERE iid = ".$lp_order[$order - 1];
4299
                Database::query($sql);
4300
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
4301
                        WHERE iid = $lp_id";
4302
                Database::query($sql);
4303
            }
4304
        }
4305
4306
        return true;
4307
    }
4308
4309
    /**
4310
     * Move a learnpath down (display_order).
4311
     *
4312
     * @param int $lp_id      Learnpath ID
4313
     * @param int $categoryId Category ID
4314
     *
4315
     * @return bool
4316
     */
4317
    public static function move_down($lp_id, $categoryId = 0)
4318
    {
4319
        $courseId = api_get_course_int_id();
4320
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4321
4322
        $categoryCondition = '';
4323
        if (!empty($categoryId)) {
4324
            $categoryId = (int) $categoryId;
4325
            $categoryCondition = " AND category_id = $categoryId";
4326
        }
4327
4328
        $sql = "SELECT * FROM $lp_table
4329
                WHERE c_id = $courseId
4330
                $categoryCondition
4331
                ORDER BY display_order";
4332
        $res = Database::query($sql);
4333
        if ($res === false) {
4334
            return false;
4335
        }
4336
        $lps = [];
4337
        $lp_order = [];
4338
        $num = Database::num_rows($res);
4339
        $max = 0;
4340
        // First check the order is correct, globally (might be wrong because
4341
        // of versions < 1.8.4).
4342
        if ($num > 0) {
4343
            $i = 1;
4344
            while ($row = Database::fetch_array($res)) {
4345
                $max = $i;
4346
                if ($row['display_order'] != $i) {
4347
                    // If we find a gap in the order, we need to fix it.
4348
                    $sql = "UPDATE $lp_table SET display_order = $i
4349
                              WHERE iid = ".$row['iid'];
4350
                    Database::query($sql);
4351
                }
4352
                $row['display_order'] = $i;
4353
                $lps[$row['iid']] = $row;
4354
                $lp_order[$i] = $row['iid'];
4355
                $i++;
4356
            }
4357
        }
4358
        if ($num > 1) { // If there's only one element, no need to sort.
4359
            $order = $lps[$lp_id]['display_order'];
4360
            if ($order < $max) { // If it's the first element, no need to move up.
4361
                $sql = "UPDATE $lp_table SET display_order = $order
4362
                        WHERE iid = ".$lp_order[$order + 1];
4363
                Database::query($sql);
4364
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4365
                        WHERE iid = $lp_id";
4366
                Database::query($sql);
4367
            }
4368
        }
4369
4370
        return true;
4371
    }
4372
4373
    /**
4374
     * Updates learnpath attributes to point to the next element
4375
     * The last part is similar to set_current_item but processing the other way around.
4376
     */
4377
    public function next()
4378
    {
4379
        if ($this->debug > 0) {
4380
            error_log('In learnpath::next()', 0);
4381
        }
4382
        $this->last = $this->get_current_item_id();
4383
        $this->items[$this->last]->save(
4384
            false,
4385
            $this->prerequisites_match($this->last)
4386
        );
4387
        $this->autocomplete_parents($this->last);
4388
        $new_index = $this->get_next_index();
4389
        if ($this->debug > 2) {
4390
            error_log('New index: '.$new_index, 0);
4391
        }
4392
        $this->index = $new_index;
4393
        if ($this->debug > 2) {
4394
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4395
        }
4396
        $this->current = $this->ordered_items[$new_index];
4397
        if ($this->debug > 2) {
4398
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4399
        }
4400
    }
4401
4402
    /**
4403
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4404
     * class, this might be redefined to allow several behaviours depending on the document type.
4405
     *
4406
     * @param int $id Resource ID
4407
     */
4408
    public function open($id)
4409
    {
4410
        // TODO:
4411
        // set the current resource attribute to this resource
4412
        // switch on element type (redefine in child class?)
4413
        // set status for this item to "opened"
4414
        // start timer
4415
        // initialise score
4416
        $this->index = 0; //or = the last item seen (see $this->last)
4417
    }
4418
4419
    /**
4420
     * Check that all prerequisites are fulfilled. Returns true and an
4421
     * empty string on success, returns false
4422
     * and the prerequisite string on error.
4423
     * This function is based on the rules for aicc_script language as
4424
     * described in the SCORM 1.2 CAM documentation page 108.
4425
     *
4426
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4427
     *
4428
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4429
     *              string otherwise
4430
     */
4431
    public function prerequisites_match($itemId = null)
4432
    {
4433
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4434
        if ($allow) {
4435
            if (api_is_allowed_to_edit() ||
4436
                api_is_platform_admin(true) ||
4437
                api_is_drh() ||
4438
                api_is_coach(api_get_session_id(), api_get_course_int_id(), false)
4439
            ) {
4440
                return true;
4441
            }
4442
        }
4443
4444
        $debug = $this->debug;
4445
        if ($debug > 0) {
4446
            error_log('In learnpath::prerequisites_match()');
4447
        }
4448
4449
        if (empty($itemId)) {
4450
            $itemId = $this->current;
4451
        }
4452
4453
        $currentItem = $this->getItem($itemId);
4454
        if ($debug > 0) {
4455
            error_log("Checking item id $itemId");
4456
        }
4457
4458
        if ($currentItem) {
4459
            if ($this->type == 2) {
4460
                // Getting prereq from scorm
4461
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4462
            } else {
4463
                $prereq_string = $currentItem->get_prereq_string();
4464
            }
4465
4466
            if (empty($prereq_string)) {
4467
                if ($debug > 0) {
4468
                    error_log('Found prereq_string is empty return true');
4469
                }
4470
                // checks the dates values as prerequisites
4471
                $result = $this->prerequistesDatesMatch($itemId);
4472
4473
                return $result;
4474
            }
4475
4476
            // Clean spaces.
4477
            $prereq_string = str_replace(' ', '', $prereq_string);
4478
            if ($debug > 0) {
4479
                error_log('Found prereq_string: '.$prereq_string, 0);
4480
            }
4481
4482
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4483
            $result = $currentItem->parse_prereq(
4484
                $prereq_string,
4485
                $this->items,
4486
                $this->refs_list,
4487
                $this->get_user_id()
4488
            );
4489
4490
            if ($result === false) {
4491
                $this->set_error_msg($currentItem->prereq_alert);
4492
            }
4493
        } else {
4494
            $result = true;
4495
            if ($debug > 1) {
4496
                error_log('$this->items['.$itemId.'] was not an object');
4497
            }
4498
        }
4499
4500
        if ($debug > 1) {
4501
            error_log('Result: '.$result);
4502
            error_log('End of prerequisites_match(). Error message is now '.$this->error);
4503
        }
4504
4505
        if (true === $result && $itemId) {
4506
            // checks the dates values as prerequisites
4507
            $result = $this->prerequistesDatesMatch($itemId);
4508
        }
4509
4510
        return $result;
4511
    }
4512
4513
    public function prerequistesDatesMatch(int $itemId)
4514
    {
4515
        if (true === api_get_configuration_value('lp_item_prerequisite_dates')) {
4516
            $extraFieldValue = new ExtraFieldValue('lp_item');
4517
            $startDate = $extraFieldValue->get_values_by_handler_and_field_variable(
4518
                $itemId,
4519
                'start_date'
4520
            );
4521
            $endDate = $extraFieldValue->get_values_by_handler_and_field_variable(
4522
                $itemId,
4523
                'end_date'
4524
            );
4525
4526
            $now = time();
4527
            $start = !empty($startDate['value']) ? api_strtotime($startDate['value']) : 0;
4528
            $end = !empty($endDate['value']) ? api_strtotime($endDate['value']) : 0;
4529
            $result = false;
4530
4531
            if (($start == 0 && $end == 0) ||
4532
                (($start > 0 && $end == 0) && $now > $start) ||
4533
                (($start == 0 && $end > 0) && $now < $end) ||
4534
                (($start > 0 && $end > 0) && ($now > $start && $now < $end))
4535
            ) {
4536
                $result = true;
4537
            }
4538
4539
            if (!$result) {
4540
                $errMsg = get_lang('ItemCanNotBeAccessedPrerequisiteDates');
4541
                if ($start > 0 && $start > $now) {
4542
                    $errMsg = get_lang('AccessibleFrom').' '.api_format_date($start, DATE_TIME_FORMAT_LONG);
4543
                }
4544
                if ($end > 0 && $end < $now) {
4545
                    $errMsg = get_lang('NoMoreAccessible');
4546
                }
4547
                $this->set_error_msg($errMsg);
4548
                $currentItem = $this->getItem($itemId);
4549
                $currentItem->prereq_alert = $errMsg;
4550
            }
4551
4552
            return $result;
4553
        }
4554
4555
        return true;
4556
    }
4557
4558
    /**
4559
     * Updates learnpath attributes to point to the previous element
4560
     * The last part is similar to set_current_item but processing the other way around.
4561
     */
4562
    public function previous()
4563
    {
4564
        $this->last = $this->get_current_item_id();
4565
        $this->items[$this->last]->save(
4566
            false,
4567
            $this->prerequisites_match($this->last)
4568
        );
4569
        $this->autocomplete_parents($this->last);
4570
        $new_index = $this->get_previous_index();
4571
        $this->index = $new_index;
4572
        $this->current = $this->ordered_items[$new_index];
4573
    }
4574
4575
    /**
4576
     * Publishes a learnpath. This basically means show or hide the learnpath
4577
     * to normal users.
4578
     * Can be used as abstract.
4579
     *
4580
     * @param int $lp_id          Learnpath ID
4581
     * @param int $set_visibility New visibility
4582
     *
4583
     * @return bool
4584
     */
4585
    public static function toggle_visibility($lp_id, $set_visibility = 1)
4586
    {
4587
        if (empty($lp_id)) {
4588
            return false;
4589
        }
4590
4591
        $action = 'visible';
4592
        if ($set_visibility != 1) {
4593
            $action = 'invisible';
4594
            self::toggle_publish($lp_id, 'i');
4595
        }
4596
4597
        return api_item_property_update(
4598
            api_get_course_info(),
4599
            TOOL_LEARNPATH,
4600
            $lp_id,
4601
            $action,
4602
            api_get_user_id()
4603
        );
4604
    }
4605
4606
    /**
4607
     * Publishes a learnpath category.
4608
     * This basically means show or hide the learnpath category to normal users.
4609
     *
4610
     * @param int $id
4611
     * @param int $visibility
4612
     *
4613
     * @return bool
4614
     */
4615
    public static function toggleCategoryVisibility($id, $visibility = 1)
4616
    {
4617
        $action = 'visible';
4618
        if ($visibility != 1) {
4619
            self::toggleCategoryPublish($id, 0);
4620
            $action = 'invisible';
4621
        }
4622
4623
        return api_item_property_update(
4624
            api_get_course_info(),
4625
            TOOL_LEARNPATH_CATEGORY,
4626
            $id,
4627
            $action,
4628
            api_get_user_id()
4629
        );
4630
    }
4631
4632
    /**
4633
     * Publishes a learnpath. This basically means show or hide the learnpath
4634
     * on the course homepage
4635
     * Can be used as abstract.
4636
     *
4637
     * @param int    $lp_id          Learnpath id
4638
     * @param string $set_visibility New visibility (v/i - visible/invisible)
4639
     *
4640
     * @return bool
4641
     */
4642
    public static function toggle_publish($lp_id, $set_visibility = 'v')
4643
    {
4644
        if (empty($lp_id)) {
4645
            return false;
4646
        }
4647
        $course_id = api_get_course_int_id();
4648
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4649
        $lp_id = (int) $lp_id;
4650
        $sql = "SELECT * FROM $tbl_lp
4651
                WHERE iid = $lp_id";
4652
        $result = Database::query($sql);
4653
        if (Database::num_rows($result)) {
4654
            $row = Database::fetch_array($result);
4655
            $name = Database::escape_string($row['name']);
4656
            if ($set_visibility === 'i') {
4657
                $v = 0;
4658
            }
4659
            if ($set_visibility === 'v') {
4660
                $v = 1;
4661
            }
4662
4663
            $session_id = api_get_session_id();
4664
            $session_condition = api_get_session_condition($session_id);
4665
4666
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4667
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4668
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4669
4670
            $extraLpCondition = '';
4671
            $extraLink = '';
4672
            if (!empty($session_id)) {
4673
                $extraLink = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session=0';
4674
                $extraLpCondition = " OR (link = '$link' AND session_id = $session_id )  ";
4675
            }
4676
4677
            $sql = "SELECT * FROM $tbl_tool
4678
                    WHERE
4679
                        c_id = $course_id AND
4680
                        (link = '$link' OR link = '$oldLink' $extraLpCondition ) AND
4681
                        image = 'scormbuilder.gif' AND
4682
                        (
4683
                            link LIKE '$link%' OR
4684
                            link LIKE '$oldLink%'
4685
                            $extraLpCondition
4686
                        )
4687
                        $session_condition
4688
                    ";
4689
4690
            $result = Database::query($sql);
4691
            $num = Database::num_rows($result);
4692
            $resultTool = Database::fetch_array($result, 'ASSOC');
4693
4694
            if ($set_visibility === 'i') {
4695
                if ($num > 0) {
4696
                    $sql = "DELETE FROM $tbl_tool
4697
                            WHERE
4698
                                c_id = $course_id AND
4699
                                (link = '$link' OR link = '$oldLink') AND
4700
                                image='scormbuilder.gif'
4701
                                $session_condition";
4702
                    Database::query($sql);
4703
                }
4704
4705
                // Disables the base course link inside a session.
4706
                if (!empty($session_id) && 0 === (int) $row['session_id']) {
4707
                    $sql = "SELECT iid FROM $tbl_tool
4708
                            WHERE
4709
                                c_id = $course_id AND
4710
                                (link = '$extraLink') AND
4711
                                image = 'scormbuilder.gif' AND
4712
                                session_id = $session_id
4713
                    ";
4714
                    $resultBaseLp = Database::query($sql);
4715
                    if (Database::num_rows($resultBaseLp)) {
4716
                        $resultBaseLpRow = Database::fetch_array($resultBaseLp);
4717
                        $id = $resultBaseLpRow['iid'];
4718
                        /*$sql = "UPDATE $tbl_tool
4719
                                SET visibility = 0
4720
                                WHERE iid = $id ";
4721
                        Database::query($sql);*/
4722
                        $sql = "DELETE FROM $tbl_tool
4723
                                WHERE iid = $id";
4724
                        Database::query($sql);
4725
                    } else {
4726
                        /*$params = [
4727
                            'category' => 'authoring',
4728
                            'c_id' => $course_id,
4729
                            'name' => $name,
4730
                            'link' => $link,
4731
                            'image' => 'scormbuilder.gif',
4732
                            'visibility' => '0',
4733
                            'admin' => '0',
4734
                            'address' => 'pastillegris.gif',
4735
                            'added_tool' => '0',
4736
                            'session_id' => $session_id,
4737
                        ];
4738
                        $insertId = Database::insert($tbl_tool, $params);
4739
                        if ($insertId) {
4740
                            $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4741
                            Database::query($sql);
4742
                        }*/
4743
                    }
4744
                }
4745
            }
4746
4747
            if ($set_visibility === 'v') {
4748
                if ($num == 0) {
4749
                    $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id)
4750
                            VALUES ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4751
                    Database::query($sql);
4752
                    $insertId = Database::insert_id();
4753
                    if ($insertId) {
4754
                        $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4755
                        Database::query($sql);
4756
                    }
4757
                }
4758
                if ($num > 0) {
4759
                    $id = $resultTool['iid'];
4760
                    $sql = "UPDATE $tbl_tool SET
4761
                        c_id = $course_id,
4762
                        name = '$name',
4763
                        link = '$link',
4764
                        image = 'scormbuilder.gif',
4765
                        visibility = '$v',
4766
                        admin = '0',
4767
                        address = 'pastillegris.gif',
4768
                        added_tool = 0,
4769
                        session_id = $session_id
4770
                    WHERE
4771
                        c_id = ".$course_id." AND
4772
                        iid = $id
4773
                    ";
4774
                    Database::query($sql);
4775
                }
4776
            }
4777
        }
4778
4779
        return false;
4780
    }
4781
4782
    /**
4783
     * Publishes a learnpath.
4784
     * Show or hide the learnpath category on the course homepage.
4785
     *
4786
     * @param int $id
4787
     * @param int $setVisibility
4788
     *
4789
     * @return bool
4790
     */
4791
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4792
    {
4793
        $courseId = api_get_course_int_id();
4794
        $sessionId = api_get_session_id();
4795
        $sessionCondition = api_get_session_condition(
4796
            $sessionId,
4797
            true,
4798
            false,
4799
            't.sessionId'
4800
        );
4801
4802
        $em = Database::getManager();
4803
        $category = self::getCategory($id);
4804
4805
        if (!$category) {
4806
            return false;
4807
        }
4808
4809
        if (empty($courseId)) {
4810
            return false;
4811
        }
4812
4813
        $link = self::getCategoryLinkForTool($id);
4814
4815
        /** @var CTool $tool */
4816
        $tool = $em->createQuery("
4817
                SELECT t FROM ChamiloCourseBundle:CTool t
4818
                WHERE
4819
                    t.cId = :course AND
4820
                    t.link = :link1 AND
4821
                    t.image = 'lp_category.gif' AND
4822
                    t.link LIKE :link2
4823
                    $sessionCondition
4824
            ")
4825
            ->setParameters([
4826
                'course' => $courseId,
4827
                'link1' => $link,
4828
                'link2' => "$link%",
4829
            ])
4830
            ->getOneOrNullResult();
4831
4832
        if ($setVisibility == 0 && $tool) {
4833
            $em->remove($tool);
4834
            $em->flush();
4835
4836
            return true;
4837
        }
4838
4839
        if ($setVisibility == 1 && !$tool) {
4840
            $tool = new CTool();
4841
            $tool
4842
                ->setCategory('authoring')
4843
                ->setCId($courseId)
4844
                ->setName(strip_tags($category->getName()))
4845
                ->setLink($link)
4846
                ->setImage('lp_category.gif')
4847
                ->setVisibility(1)
4848
                ->setAdmin(0)
4849
                ->setAddress('pastillegris.gif')
4850
                ->setAddedTool(0)
4851
                ->setSessionId($sessionId)
4852
                ->setTarget('_self');
4853
4854
            $em->persist($tool);
4855
            $em->flush();
4856
4857
            $tool->setId($tool->getIid());
4858
4859
            $em->persist($tool);
4860
            $em->flush();
4861
4862
            return true;
4863
        }
4864
4865
        if ($setVisibility == 1 && $tool) {
4866
            $tool
4867
                ->setName(strip_tags($category->getName()))
4868
                ->setVisibility(1);
4869
4870
            $em->persist($tool);
4871
            $em->flush();
4872
4873
            return true;
4874
        }
4875
4876
        return false;
4877
    }
4878
4879
    /**
4880
     * Check if the learnpath category is visible for a user.
4881
     *
4882
     * @param int $courseId
4883
     * @param int $sessionId
4884
     */
4885
    public static function categoryIsVisibleForStudent(
4886
        ?CLpCategory $category,
4887
        User $user,
4888
        $courseId = 0,
4889
        $sessionId = 0
4890
    ): bool {
4891
        if (empty($category)) {
4892
            return false;
4893
        }
4894
4895
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4896
4897
        if ($isAllowedToEdit) {
4898
            return true;
4899
        }
4900
4901
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4902
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4903
4904
        $courseInfo = api_get_course_info_by_id($courseId);
4905
4906
        $categoryVisibility = api_get_item_visibility(
4907
            $courseInfo,
4908
            TOOL_LEARNPATH_CATEGORY,
4909
            $category->getId(),
4910
            $sessionId
4911
        );
4912
4913
        if ($categoryVisibility !== 1 && $categoryVisibility != -1) {
4914
            return false;
4915
        }
4916
4917
        $subscriptionSettings = self::getSubscriptionSettings();
4918
4919
        if (!$subscriptionSettings['allow_add_users_to_lp_category']) {
4920
            return true;
4921
        }
4922
4923
        $noUserSubscribed = false;
4924
        $noGroupSubscribed = true;
4925
        $users = $category->getUsers();
4926
        if (empty($users) || !$users->count()) {
4927
            $noUserSubscribed = true;
4928
        } elseif ($category->hasUserAdded($user)) {
4929
            return true;
4930
        }
4931
4932
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4933
        $em = Database::getManager();
4934
4935
        /** @var ItemPropertyRepository $itemRepo */
4936
        $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4937
4938
        /** @var CourseRepository $courseRepo */
4939
        $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4940
        $session = null;
4941
        if (!empty($sessionId)) {
4942
            $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4943
        }
4944
4945
        $course = $courseRepo->find($courseId);
4946
4947
        if ($courseId != 0) {
4948
            // Subscribed groups to a LP
4949
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4950
                TOOL_LEARNPATH_CATEGORY,
4951
                $category->getId(),
4952
                $course,
4953
                $session
4954
            );
4955
        }
4956
4957
        if (!empty($subscribedGroupsInLp)) {
4958
            $noGroupSubscribed = false;
4959
            if (!empty($groups)) {
4960
                $groups = array_column($groups, 'iid');
4961
                /** @var CItemProperty $item */
4962
                foreach ($subscribedGroupsInLp as $item) {
4963
                    if ($item->getGroup() &&
4964
                        in_array($item->getGroup()->getId(), $groups)
4965
                    ) {
4966
                        return true;
4967
                    }
4968
                }
4969
            }
4970
        }
4971
4972
        return $noGroupSubscribed && $noUserSubscribed;
4973
    }
4974
4975
    /**
4976
     * Check if a learnpath category is published as course tool.
4977
     *
4978
     * @param int $courseId
4979
     *
4980
     * @return bool
4981
     */
4982
    public static function categoryIsPublished(CLpCategory $category, $courseId)
4983
    {
4984
        $link = self::getCategoryLinkForTool($category->getId());
4985
        $em = Database::getManager();
4986
4987
        $tools = $em
4988
            ->createQuery("
4989
                SELECT t FROM ChamiloCourseBundle:CTool t
4990
                WHERE t.cId = :course AND
4991
                    t.name = :name AND
4992
                    t.image = 'lp_category.gif' AND
4993
                    t.link LIKE :link
4994
            ")
4995
            ->setParameters([
4996
                'course' => $courseId,
4997
                'name' => strip_tags($category->getName()),
4998
                'link' => "$link%",
4999
            ])
5000
            ->getResult();
5001
5002
        /** @var CTool $tool */
5003
        $tool = current($tools);
5004
5005
        return $tool ? $tool->getVisibility() : false;
5006
    }
5007
5008
    /**
5009
     * Restart the whole learnpath. Return the URL of the first element.
5010
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
5011
     * To use a similar method  statically, use the create_new_attempt() method.
5012
     *
5013
     * @return bool
5014
     */
5015
    public function restart()
5016
    {
5017
        if ($this->debug > 0) {
5018
            error_log('In learnpath::restart()', 0);
5019
        }
5020
        // TODO
5021
        // Call autosave method to save the current progress.
5022
        //$this->index = 0;
5023
        if (api_is_invitee()) {
5024
            return false;
5025
        }
5026
        $session_id = api_get_session_id();
5027
        $course_id = api_get_course_int_id();
5028
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
5029
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
5030
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
5031
        if ($this->debug > 2) {
5032
            error_log('Inserting new lp_view for restart: '.$sql, 0);
5033
        }
5034
        Database::query($sql);
5035
        $view_id = Database::insert_id();
5036
5037
        if ($view_id) {
5038
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
5039
            Database::query($sql);
5040
            $this->lp_view_id = $view_id;
5041
            $this->attempt = $this->attempt + 1;
5042
        } else {
5043
            $this->error = 'Could not insert into item_view table...';
5044
5045
            return false;
5046
        }
5047
        $this->autocomplete_parents($this->current);
5048
        foreach ($this->items as $index => $dummy) {
5049
            $this->items[$index]->restart();
5050
            $this->items[$index]->set_lp_view($this->lp_view_id);
5051
        }
5052
        $this->first();
5053
5054
        return true;
5055
    }
5056
5057
    /**
5058
     * Saves the current item.
5059
     *
5060
     * @return bool
5061
     */
5062
    public function save_current()
5063
    {
5064
        $debug = $this->debug;
5065
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
5066
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
5067
        if ($debug) {
5068
            error_log('save_current() saving item '.$this->current, 0);
5069
            error_log(''.print_r($this->items, true), 0);
5070
        }
5071
        if (isset($this->items[$this->current]) &&
5072
            is_object($this->items[$this->current])
5073
        ) {
5074
            if ($debug) {
5075
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
5076
            }
5077
5078
            $res = $this->items[$this->current]->save(
5079
                false,
5080
                $this->prerequisites_match($this->current)
5081
            );
5082
            $this->autocomplete_parents($this->current);
5083
            $status = $this->items[$this->current]->get_status();
5084
            $this->update_queue[$this->current] = $status;
5085
5086
            if ($debug) {
5087
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
5088
            }
5089
5090
            return $res;
5091
        }
5092
5093
        return false;
5094
    }
5095
5096
    /**
5097
     * Saves the given item.
5098
     *
5099
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
5100
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
5101
     *
5102
     * @return bool
5103
     */
5104
    public function save_item($item_id = null, $from_outside = true)
5105
    {
5106
        $debug = $this->debug;
5107
        if ($debug) {
5108
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
5109
        }
5110
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
5111
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
5112
        if (empty($item_id)) {
5113
            $item_id = (int) $_REQUEST['id'];
5114
        }
5115
5116
        if (empty($item_id)) {
5117
            $item_id = $this->get_current_item_id();
5118
        }
5119
        if (isset($this->items[$item_id]) &&
5120
            is_object($this->items[$item_id])
5121
        ) {
5122
            // Saving the item.
5123
            $res = $this->items[$item_id]->save(
5124
                $from_outside,
5125
                $this->prerequisites_match($item_id)
5126
            );
5127
5128
            if ($debug) {
5129
                error_log('update_queue before:');
5130
                error_log(print_r($this->update_queue, 1));
5131
            }
5132
            $this->autocomplete_parents($item_id);
5133
5134
            $status = $this->items[$item_id]->get_status();
5135
            $this->update_queue[$item_id] = $status;
5136
5137
            if ($debug) {
5138
                error_log('get_status(): '.$status);
5139
                error_log('update_queue after:');
5140
                error_log(print_r($this->update_queue, 1));
5141
            }
5142
5143
            return $res;
5144
        }
5145
5146
        return false;
5147
    }
5148
5149
    /**
5150
     * Update the last progress only in case.
5151
     */
5152
    public function updateLpProgress()
5153
    {
5154
        $debug = $this->debug;
5155
        if ($debug) {
5156
            error_log('In learnpath::updateLpProgress()', 0);
5157
        }
5158
        $sessionCondition = api_get_session_condition(
5159
            api_get_session_id(),
5160
            true,
5161
            false
5162
        );
5163
        $courseId = api_get_course_int_id();
5164
        $userId = $this->get_user_id();
5165
        $table = Database::get_course_table(TABLE_LP_VIEW);
5166
5167
        [$progress] = $this->get_progress_bar_text('%');
5168
        if ($progress >= 0 && $progress <= 100) {
5169
            // Check database.
5170
            $progress = (int) $progress;
5171
            $sql = "UPDATE $table SET
5172
                            progress = $progress
5173
                        WHERE
5174
                            c_id = $courseId AND
5175
                            lp_id = ".$this->get_id()." AND
5176
                            progress < $progress AND
5177
                            user_id = ".$userId." ".$sessionCondition;
5178
            // Ignore errors as some tables might not have the progress field just yet.
5179
            Database::query($sql);
5180
            if ($debug) {
5181
                error_log($sql);
5182
            }
5183
            $this->progress_db = $progress;
5184
5185
            if (100 == $progress) {
5186
                HookLearningPathEnd::create()
5187
                    ->setEventData(['lp_view_id' => $this->lp_view_id])
5188
                    ->hookLearningPathEnd();
5189
            }
5190
        }
5191
    }
5192
5193
    /**
5194
     * Saves the last item seen's ID only in case.
5195
     */
5196
    public function save_last($score = null)
5197
    {
5198
        $course_id = api_get_course_int_id();
5199
        $debug = $this->debug;
5200
        if ($debug) {
5201
            error_log('In learnpath::save_last()', 0);
5202
        }
5203
        $session_condition = api_get_session_condition(
5204
            api_get_session_id(),
5205
            true,
5206
            false
5207
        );
5208
        $table = Database::get_course_table(TABLE_LP_VIEW);
5209
5210
        $userId = $this->get_user_id();
5211
        if (empty($userId)) {
5212
            $userId = api_get_user_id();
5213
            if ($debug) {
5214
                error_log('$this->get_user_id() was empty, used api_get_user_id() instead in '.__FILE__.' line '.__LINE__);
5215
            }
5216
        }
5217
        if (isset($this->current) && !api_is_invitee()) {
5218
            if ($debug) {
5219
                error_log('Saving current item ('.$this->current.') for later review', 0);
5220
            }
5221
            $sql = "UPDATE $table SET
5222
                        last_item = ".$this->get_current_item_id()."
5223
                    WHERE
5224
                        c_id = $course_id AND
5225
                        lp_id = ".$this->get_id()." AND
5226
                        user_id = ".$userId." ".$session_condition;
5227
            if ($debug) {
5228
                error_log('Saving last item seen : '.$sql, 0);
5229
            }
5230
            Database::query($sql);
5231
        }
5232
5233
        if (!api_is_invitee()) {
5234
            // Save progress.
5235
            [$progress] = $this->get_progress_bar_text('%');
5236
            $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
5237
            $scoreAsProgress = $this->getUseScoreAsProgress();
5238
            if ($scoreAsProgress && $scoreAsProgressSetting && (null === $score || empty($score) || -1 == $score)) {
5239
                if ($debug) {
5240
                    error_log("Return false: Dont save score: $score");
5241
                    error_log("progress: $progress");
5242
                }
5243
5244
                return false;
5245
            }
5246
5247
            if ($scoreAsProgress && $scoreAsProgressSetting) {
5248
                $storedProgress = self::getProgress(
5249
                    $this->get_id(),
5250
                    $userId,
5251
                    $course_id,
5252
                    $this->get_lp_session_id()
5253
                );
5254
5255
                // Check if the stored progress is higher than the new value
5256
                if ($storedProgress >= $progress) {
5257
                    if ($debug) {
5258
                        error_log("Return false: New progress value is lower than stored value - Current value: $storedProgress - New value: $progress [lp ".$this->get_id()." - user ".$userId."]");
5259
                    }
5260
5261
                    return false;
5262
                }
5263
            }
5264
5265
            if ($progress >= 0 && $progress <= 100) {
5266
                // Check database.
5267
                $progress = (int) $progress;
5268
                $sql = "UPDATE $table SET
5269
                            progress = $progress
5270
                        WHERE
5271
                            c_id = $course_id AND
5272
                            lp_id = ".$this->get_id()." AND
5273
                            user_id = ".$userId." ".$session_condition;
5274
                // Ignore errors as some tables might not have the progress field just yet.
5275
                Database::query($sql);
5276
                if ($debug) {
5277
                    error_log($sql);
5278
                }
5279
                $this->progress_db = $progress;
5280
5281
                if (100 == $progress) {
5282
                    HookLearningPathEnd::create()
5283
                        ->setEventData(['lp_view_id' => $this->lp_view_id])
5284
                        ->hookLearningPathEnd();
5285
                }
5286
            }
5287
        }
5288
    }
5289
5290
    /**
5291
     * Sets the current item ID (checks if valid and authorized first).
5292
     *
5293
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
5294
     */
5295
    public function set_current_item($item_id = null)
5296
    {
5297
        $debug = $this->debug;
5298
        if ($debug) {
5299
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
5300
        }
5301
        if (empty($item_id)) {
5302
            if ($debug) {
5303
                error_log('No new current item given, ignore...', 0);
5304
            }
5305
            // Do nothing.
5306
        } else {
5307
            if ($debug) {
5308
                error_log('New current item given is '.$item_id.'...', 0);
5309
            }
5310
            if (is_numeric($item_id)) {
5311
                $item_id = (int) $item_id;
5312
                // TODO: Check in database here.
5313
                $this->last = $this->current;
5314
                $this->current = $item_id;
5315
                // TODO: Update $this->index as well.
5316
                foreach ($this->ordered_items as $index => $item) {
5317
                    if ($item == $this->current) {
5318
                        $this->index = $index;
5319
                        break;
5320
                    }
5321
                }
5322
                if ($debug) {
5323
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
5324
                }
5325
            } else {
5326
                if ($debug) {
5327
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
5328
                }
5329
            }
5330
        }
5331
    }
5332
5333
    /**
5334
     * Sets the encoding.
5335
     *
5336
     * @param string $enc New encoding
5337
     *
5338
     * @return bool
5339
     *
5340
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
5341
     */
5342
    public function set_encoding($enc = 'UTF-8')
5343
    {
5344
        $enc = api_refine_encoding_id($enc);
5345
        if (empty($enc)) {
5346
            $enc = api_get_system_encoding();
5347
        }
5348
        if (api_is_encoding_supported($enc)) {
5349
            $lp = $this->get_id();
5350
            if ($lp != 0) {
5351
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5352
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
5353
                        WHERE iid = ".$lp;
5354
                $res = Database::query($sql);
5355
5356
                return $res;
5357
            }
5358
        }
5359
5360
        return false;
5361
    }
5362
5363
    /**
5364
     * Sets the JS lib setting in the database directly.
5365
     * This is the JavaScript library file this lp needs to load on startup.
5366
     *
5367
     * @param string $lib Proximity setting
5368
     *
5369
     * @return bool True on update success. False otherwise.
5370
     */
5371
    public function set_jslib($lib = '')
5372
    {
5373
        $lp = $this->get_id();
5374
5375
        if ($lp != 0) {
5376
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5377
            $lib = Database::escape_string($lib);
5378
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
5379
                    WHERE iid = $lp";
5380
            $res = Database::query($sql);
5381
5382
            return $res;
5383
        }
5384
5385
        return false;
5386
    }
5387
5388
    /**
5389
     * Sets the name of the LP maker (publisher) (and save).
5390
     *
5391
     * @param string $name Optional string giving the new content_maker of this learnpath
5392
     *
5393
     * @return bool True
5394
     */
5395
    public function set_maker($name = '')
5396
    {
5397
        if (empty($name)) {
5398
            return false;
5399
        }
5400
        $this->maker = $name;
5401
        $table = Database::get_course_table(TABLE_LP_MAIN);
5402
        $lp_id = $this->get_id();
5403
        $sql = "UPDATE $table SET
5404
                content_maker = '".Database::escape_string($this->maker)."'
5405
                WHERE iid = $lp_id";
5406
        Database::query($sql);
5407
5408
        return true;
5409
    }
5410
5411
    /**
5412
     * Sets the name of the current learnpath (and save).
5413
     *
5414
     * @param string $name Optional string giving the new name of this learnpath
5415
     *
5416
     * @return bool True/False
5417
     */
5418
    public function set_name($name = null)
5419
    {
5420
        if (empty($name)) {
5421
            return false;
5422
        }
5423
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5424
        $name = Database::escape_string($name);
5425
5426
        $this->name = $name;
5427
5428
        $lp_id = $this->get_id();
5429
        $course_id = $this->course_info['real_id'];
5430
        $sql = "UPDATE $lp_table SET
5431
                name = '$name'
5432
                WHERE iid = $lp_id";
5433
        $result = Database::query($sql);
5434
        // If the lp is visible on the homepage, change his name there.
5435
        if (Database::affected_rows($result)) {
5436
            $session_id = api_get_session_id();
5437
            $session_condition = api_get_session_condition($session_id);
5438
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
5439
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
5440
            $sql = "UPDATE $tbl_tool SET name = '$name'
5441
            	    WHERE
5442
            	        c_id = $course_id AND
5443
            	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
5444
            Database::query($sql);
5445
5446
            return true;
5447
        }
5448
5449
        return false;
5450
    }
5451
5452
    /**
5453
     * Set index specified prefix terms for all items in this path.
5454
     *
5455
     * @param string $terms_string Comma-separated list of terms
5456
     * @param string $prefix       Xapian term prefix
5457
     *
5458
     * @return bool False on error, true otherwise
5459
     */
5460
    public function set_terms_by_prefix($terms_string, $prefix)
5461
    {
5462
        $course_id = api_get_course_int_id();
5463
        if (api_get_setting('search_enabled') !== 'true') {
5464
            return false;
5465
        }
5466
5467
        if (!extension_loaded('xapian')) {
5468
            return false;
5469
        }
5470
5471
        $terms_string = trim($terms_string);
5472
        $terms = explode(',', $terms_string);
5473
        array_walk($terms, 'trim_value');
5474
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5475
5476
        // Don't do anything if no change, verify only at DB, not the search engine.
5477
        if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
5478
            return false;
5479
        }
5480
5481
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5482
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5483
5484
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5485
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5486
        $lp_id = (int) $_POST['lp_id'];
5487
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5488
        $result = Database::query($sql);
5489
        $di = new ChamiloIndexer();
5490
5491
        while ($lp_item = Database::fetch_array($result)) {
5492
            // Get search_did.
5493
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5494
            $sql = 'SELECT * FROM %s
5495
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5496
                    LIMIT 1';
5497
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5498
5499
            //echo $sql; echo '<br>';
5500
            $res = Database::query($sql);
5501
            if (Database::num_rows($res) > 0) {
5502
                $se_ref = Database::fetch_array($res);
5503
                // Compare terms.
5504
                $doc = $di->get_document($se_ref['search_did']);
5505
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5506
                $xterms = [];
5507
                foreach ($xapian_terms as $xapian_term) {
5508
                    $xterms[] = substr($xapian_term['name'], 1);
5509
                }
5510
5511
                $dterms = $terms;
5512
                $missing_terms = array_diff($dterms, $xterms);
5513
                $deprecated_terms = array_diff($xterms, $dterms);
5514
5515
                // Save it to search engine.
5516
                foreach ($missing_terms as $term) {
5517
                    $doc->add_term($prefix.$term, 1);
5518
                }
5519
                foreach ($deprecated_terms as $term) {
5520
                    $doc->remove_term($prefix.$term);
5521
                }
5522
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5523
                $di->getDb()->flush();
5524
            }
5525
        }
5526
5527
        return true;
5528
    }
5529
5530
    /**
5531
     * Sets the theme of the LP (local/remote) (and save).
5532
     *
5533
     * @param string $name Optional string giving the new theme of this learnpath
5534
     *
5535
     * @return bool Returns true if theme name is not empty
5536
     */
5537
    public function set_theme($name = '')
5538
    {
5539
        $this->theme = $name;
5540
        $table = Database::get_course_table(TABLE_LP_MAIN);
5541
        $lp_id = $this->get_id();
5542
        $sql = "UPDATE $table
5543
                SET theme = '".Database::escape_string($this->theme)."'
5544
                WHERE iid = $lp_id";
5545
        Database::query($sql);
5546
5547
        return true;
5548
    }
5549
5550
    /**
5551
     * Sets the image of an LP (and save).
5552
     *
5553
     * @param string $name Optional string giving the new image of this learnpath
5554
     *
5555
     * @return bool Returns true if theme name is not empty
5556
     */
5557
    public function set_preview_image($name = '')
5558
    {
5559
        $this->preview_image = $name;
5560
        $table = Database::get_course_table(TABLE_LP_MAIN);
5561
        $lp_id = $this->get_id();
5562
        $sql = "UPDATE $table SET
5563
                preview_image = '".Database::escape_string($this->preview_image)."'
5564
                WHERE iid = $lp_id";
5565
        Database::query($sql);
5566
5567
        return true;
5568
    }
5569
5570
    /**
5571
     * Sets the author of a LP (and save).
5572
     *
5573
     * @param string $name Optional string giving the new author of this learnpath
5574
     *
5575
     * @return bool Returns true if author's name is not empty
5576
     */
5577
    public function set_author($name = '')
5578
    {
5579
        $this->author = $name;
5580
        $table = Database::get_course_table(TABLE_LP_MAIN);
5581
        $lp_id = $this->get_id();
5582
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5583
                WHERE iid = $lp_id";
5584
        Database::query($sql);
5585
5586
        return true;
5587
    }
5588
5589
    /**
5590
     * Sets the hide_toc_frame parameter of a LP (and save).
5591
     *
5592
     * @param int $hide 1 if frame is hidden 0 then else
5593
     *
5594
     * @return bool Returns true if author's name is not empty
5595
     */
5596
    public function set_hide_toc_frame($hide)
5597
    {
5598
        if (intval($hide) == $hide) {
5599
            $this->hide_toc_frame = $hide;
5600
            $table = Database::get_course_table(TABLE_LP_MAIN);
5601
            $lp_id = $this->get_id();
5602
            $sql = "UPDATE $table SET
5603
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5604
                    WHERE iid = $lp_id";
5605
            Database::query($sql);
5606
5607
            return true;
5608
        }
5609
5610
        return false;
5611
    }
5612
5613
    /**
5614
     * Sets the prerequisite of a LP (and save).
5615
     *
5616
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5617
     *
5618
     * @return bool returns true if prerequisite is not empty
5619
     */
5620
    public function set_prerequisite($prerequisite)
5621
    {
5622
        $this->prerequisite = (int) $prerequisite;
5623
        $table = Database::get_course_table(TABLE_LP_MAIN);
5624
        $lp_id = $this->get_id();
5625
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5626
                WHERE iid = $lp_id";
5627
        Database::query($sql);
5628
5629
        return true;
5630
    }
5631
5632
    /**
5633
     * Sets the location/proximity of the LP (local/remote) (and save).
5634
     *
5635
     * @param string $name Optional string giving the new location of this learnpath
5636
     *
5637
     * @return bool True on success / False on error
5638
     */
5639
    public function set_proximity($name = '')
5640
    {
5641
        if (empty($name)) {
5642
            return false;
5643
        }
5644
5645
        $this->proximity = $name;
5646
        $table = Database::get_course_table(TABLE_LP_MAIN);
5647
        $lp_id = $this->get_id();
5648
        $sql = "UPDATE $table SET
5649
                    content_local = '".Database::escape_string($name)."'
5650
                WHERE iid = $lp_id";
5651
        Database::query($sql);
5652
5653
        return true;
5654
    }
5655
5656
    /**
5657
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5658
     *
5659
     * @param int $id DB ID of the item
5660
     */
5661
    public function set_previous_item($id)
5662
    {
5663
        if ($this->debug > 0) {
5664
            error_log('In learnpath::set_previous_item()', 0);
5665
        }
5666
        $this->last = $id;
5667
    }
5668
5669
    /**
5670
     * Sets use_max_score.
5671
     *
5672
     * @param int $use_max_score Optional string giving the new location of this learnpath
5673
     *
5674
     * @return bool True on success / False on error
5675
     */
5676
    public function set_use_max_score($use_max_score = 1)
5677
    {
5678
        $use_max_score = (int) $use_max_score;
5679
        $this->use_max_score = $use_max_score;
5680
        $table = Database::get_course_table(TABLE_LP_MAIN);
5681
        $lp_id = $this->get_id();
5682
        $sql = "UPDATE $table SET
5683
                    use_max_score = '".$this->use_max_score."'
5684
                WHERE iid = $lp_id";
5685
        Database::query($sql);
5686
5687
        return true;
5688
    }
5689
5690
    /**
5691
     * Sets and saves the expired_on date.
5692
     *
5693
     * @param string $expired_on Optional string giving the new author of this learnpath
5694
     *
5695
     * @throws \Doctrine\ORM\OptimisticLockException
5696
     *
5697
     * @return bool Returns true if author's name is not empty
5698
     */
5699
    public function set_expired_on($expired_on)
5700
    {
5701
        $em = Database::getManager();
5702
        /** @var CLp $lp */
5703
        $lp = $em
5704
            ->getRepository('ChamiloCourseBundle:CLp')
5705
            ->findOneBy(
5706
                [
5707
                    'iid' => $this->get_id(),
5708
                ]
5709
            );
5710
5711
        if (!$lp) {
5712
            return false;
5713
        }
5714
5715
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5716
5717
        $lp->setExpiredOn($this->expired_on);
5718
        $em->persist($lp);
5719
        $em->flush();
5720
5721
        return true;
5722
    }
5723
5724
    /**
5725
     * Sets and saves the publicated_on date.
5726
     *
5727
     * @param string $publicated_on Optional string giving the new author of this learnpath
5728
     *
5729
     * @throws \Doctrine\ORM\OptimisticLockException
5730
     *
5731
     * @return bool Returns true if author's name is not empty
5732
     */
5733
    public function set_publicated_on($publicated_on)
5734
    {
5735
        $em = Database::getManager();
5736
        /** @var CLp $lp */
5737
        $lp = $em
5738
            ->getRepository('ChamiloCourseBundle:CLp')
5739
            ->findOneBy(
5740
                [
5741
                    'iid' => $this->get_id(),
5742
                ]
5743
            );
5744
5745
        if (!$lp) {
5746
            return false;
5747
        }
5748
5749
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5750
        $lp->setPublicatedOn($this->publicated_on);
5751
        $em->persist($lp);
5752
        $em->flush();
5753
5754
        return true;
5755
    }
5756
5757
    /**
5758
     * Sets and saves the expired_on date.
5759
     *
5760
     * @return bool Returns true if author's name is not empty
5761
     */
5762
    public function set_modified_on()
5763
    {
5764
        $this->modified_on = api_get_utc_datetime();
5765
        $table = Database::get_course_table(TABLE_LP_MAIN);
5766
        $lp_id = $this->get_id();
5767
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5768
                WHERE iid = $lp_id";
5769
        Database::query($sql);
5770
5771
        return true;
5772
    }
5773
5774
    /**
5775
     * Sets the object's error message.
5776
     *
5777
     * @param string $error Error message. If empty, reinits the error string
5778
     */
5779
    public function set_error_msg($error = '')
5780
    {
5781
        if ($this->debug > 0) {
5782
            error_log('In learnpath::set_error_msg()', 0);
5783
        }
5784
        if (empty($error)) {
5785
            $this->error = '';
5786
        } else {
5787
            $this->error .= $error;
5788
        }
5789
    }
5790
5791
    /**
5792
     * Launches the current item if not 'sco'
5793
     * (starts timer and make sure there is a record ready in the DB).
5794
     *
5795
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5796
     *
5797
     * @return bool
5798
     */
5799
    public function start_current_item($allow_new_attempt = false)
5800
    {
5801
        $debug = $this->debug;
5802
        if ($debug) {
5803
            error_log('In learnpath::start_current_item()');
5804
            error_log('current: '.$this->current);
5805
        }
5806
        if ($this->current != 0 && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5807
            $type = $this->get_type();
5808
            $item_type = $this->items[$this->current]->get_type();
5809
            if (($type == 2 && $item_type != 'sco') ||
5810
                ($type == 3 && $item_type != 'au') ||
5811
                (
5812
                    $type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES &&
5813
                    WhispeakAuthPlugin::isAllowedToSaveLpItem($this->current)
5814
                )
5815
            ) {
5816
                if ($debug) {
5817
                    error_log('item type: '.$item_type);
5818
                    error_log('lp type: '.$type);
5819
                }
5820
                $this->items[$this->current]->open($allow_new_attempt);
5821
                $this->autocomplete_parents($this->current);
5822
                $prereq_check = $this->prerequisites_match($this->current);
5823
                if ($debug) {
5824
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5825
                }
5826
5827
                $saveStatus = learnpathItem::isLpItemAutoComplete($this->current);
5828
                if ($saveStatus) {
5829
                    $this->items[$this->current]->save(false, $prereq_check);
5830
                }
5831
            }
5832
            // If sco, then it is supposed to have been updated by some other call.
5833
            if ($item_type === 'sco') {
5834
                $this->items[$this->current]->restart();
5835
            }
5836
        }
5837
        if ($debug) {
5838
            error_log('lp_view_session_id: '.$this->lp_view_session_id);
5839
            error_log('api_get_session_id: '.api_get_session_id());
5840
            error_log('End of learnpath::start_current_item()');
5841
        }
5842
5843
        return true;
5844
    }
5845
5846
    /**
5847
     * Stops the processing and counters for the old item (as held in $this->last).
5848
     *
5849
     * @return bool True/False
5850
     */
5851
    public function stop_previous_item()
5852
    {
5853
        $debug = $this->debug;
5854
        if ($debug) {
5855
            error_log('In learnpath::stop_previous_item()');
5856
        }
5857
5858
        if ($this->last != 0 && $this->last != $this->current &&
5859
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5860
        ) {
5861
            if ($debug) {
5862
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5863
            }
5864
            switch ($this->get_type()) {
5865
                case '3':
5866
                    if ($this->items[$this->last]->get_type() != 'au') {
5867
                        if ($debug) {
5868
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5869
                        }
5870
                        $this->items[$this->last]->close();
5871
                    } else {
5872
                        if ($debug) {
5873
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5874
                        }
5875
                    }
5876
                    break;
5877
                case '2':
5878
                    if ($this->items[$this->last]->get_type() != 'sco') {
5879
                        if ($debug) {
5880
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5881
                        }
5882
                        $this->items[$this->last]->close();
5883
                    } else {
5884
                        if ($debug) {
5885
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5886
                        }
5887
                    }
5888
                    break;
5889
                case '1':
5890
                default:
5891
                    if ($debug) {
5892
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5893
                    }
5894
                    $this->items[$this->last]->close();
5895
                    break;
5896
            }
5897
        } else {
5898
            if ($debug) {
5899
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5900
            }
5901
5902
            return false;
5903
        }
5904
5905
        return true;
5906
    }
5907
5908
    /**
5909
     * Updates the default view mode from fullscreen to embedded and inversely.
5910
     *
5911
     * @return string The current default view mode ('fullscreen' or 'embedded')
5912
     */
5913
    public function update_default_view_mode()
5914
    {
5915
        $table = Database::get_course_table(TABLE_LP_MAIN);
5916
        $sql = "SELECT * FROM $table
5917
                WHERE iid = ".$this->get_id();
5918
        $res = Database::query($sql);
5919
        if (Database::num_rows($res) > 0) {
5920
            $row = Database::fetch_array($res);
5921
            $default_view_mode = $row['default_view_mod'];
5922
            $view_mode = $default_view_mode;
5923
            switch ($default_view_mode) {
5924
                case 'fullscreen': // default with popup
5925
                    $view_mode = 'embedded';
5926
                    break;
5927
                case 'embedded': // default view with left menu
5928
                    $view_mode = 'embedframe';
5929
                    break;
5930
                case 'embedframe': //folded menu
5931
                    $view_mode = 'impress';
5932
                    break;
5933
                case 'impress':
5934
                    $view_mode = 'fullscreen';
5935
                    break;
5936
            }
5937
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5938
                    WHERE iid = ".$this->get_id();
5939
            Database::query($sql);
5940
            $this->mode = $view_mode;
5941
5942
            return $view_mode;
5943
        }
5944
5945
        return -1;
5946
    }
5947
5948
    /**
5949
     * Updates the default behaviour about auto-commiting SCORM updates.
5950
     *
5951
     * @return bool True if auto-commit has been set to 'on', false otherwise
5952
     */
5953
    public function update_default_scorm_commit()
5954
    {
5955
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5956
        $sql = "SELECT * FROM $lp_table
5957
                WHERE iid = ".$this->get_id();
5958
        $res = Database::query($sql);
5959
        if (Database::num_rows($res) > 0) {
5960
            $row = Database::fetch_array($res);
5961
            $force = $row['force_commit'];
5962
            if ($force == 1) {
5963
                $force = 0;
5964
                $force_return = false;
5965
            } elseif ($force == 0) {
5966
                $force = 1;
5967
                $force_return = true;
5968
            }
5969
            $sql = "UPDATE $lp_table SET force_commit = $force
5970
                    WHERE iid = ".$this->get_id();
5971
            Database::query($sql);
5972
            $this->force_commit = $force_return;
5973
5974
            return $force_return;
5975
        }
5976
5977
        return -1;
5978
    }
5979
5980
    /**
5981
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5982
     *
5983
     * @return bool True on success, false on failure
5984
     */
5985
    public function update_display_order()
5986
    {
5987
        $course_id = api_get_course_int_id();
5988
        $table = Database::get_course_table(TABLE_LP_MAIN);
5989
        $sql = "SELECT * FROM $table
5990
                WHERE c_id = $course_id
5991
                ORDER BY display_order";
5992
        $res = Database::query($sql);
5993
        if ($res === false) {
5994
            return false;
5995
        }
5996
5997
        $num = Database::num_rows($res);
5998
        // First check the order is correct, globally (might be wrong because
5999
        // of versions < 1.8.4).
6000
        if ($num > 0) {
6001
            $i = 1;
6002
            while ($row = Database::fetch_array($res)) {
6003
                if ($row['display_order'] != $i) {
6004
                    // If we find a gap in the order, we need to fix it.
6005
                    $sql = "UPDATE $table SET display_order = $i
6006
                            WHERE iid = ".$row['iid'];
6007
                    Database::query($sql);
6008
                }
6009
                $i++;
6010
            }
6011
        }
6012
6013
        return true;
6014
    }
6015
6016
    /**
6017
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
6018
     *
6019
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
6020
     */
6021
    public function update_reinit()
6022
    {
6023
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
6024
        $sql = "SELECT * FROM $lp_table
6025
                WHERE iid = ".$this->get_id();
6026
        $res = Database::query($sql);
6027
        if (Database::num_rows($res) > 0) {
6028
            $row = Database::fetch_array($res);
6029
            $force = $row['prevent_reinit'];
6030
            if ($force == 1) {
6031
                $force = 0;
6032
            } elseif ($force == 0) {
6033
                $force = 1;
6034
            }
6035
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
6036
                    WHERE iid = ".$this->get_id();
6037
            Database::query($sql);
6038
            $this->prevent_reinit = $force;
6039
6040
            return $force;
6041
        }
6042
6043
        return -1;
6044
    }
6045
6046
    /**
6047
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
6048
     *
6049
     * @return string 'single', 'multi' or 'seriousgame'
6050
     *
6051
     * @author ndiechburg <[email protected]>
6052
     */
6053
    public function get_attempt_mode()
6054
    {
6055
        //Set default value for seriousgame_mode
6056
        if (!isset($this->seriousgame_mode)) {
6057
            $this->seriousgame_mode = 0;
6058
        }
6059
        // Set default value for prevent_reinit
6060
        if (!isset($this->prevent_reinit)) {
6061
            $this->prevent_reinit = 1;
6062
        }
6063
        if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
6064
            return 'seriousgame';
6065
        }
6066
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
6067
            return 'single';
6068
        }
6069
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
6070
            return 'multiple';
6071
        }
6072
6073
        return 'single';
6074
    }
6075
6076
    /**
6077
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
6078
     *
6079
     * @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...
6080
     *
6081
     * @return bool
6082
     *
6083
     * @author ndiechburg <[email protected]>
6084
     */
6085
    public function set_attempt_mode($mode)
6086
    {
6087
        switch ($mode) {
6088
            case 'seriousgame':
6089
                $sg_mode = 1;
6090
                $prevent_reinit = 1;
6091
                break;
6092
            case 'single':
6093
                $sg_mode = 0;
6094
                $prevent_reinit = 1;
6095
                break;
6096
            case 'multiple':
6097
                $sg_mode = 0;
6098
                $prevent_reinit = 0;
6099
                break;
6100
            default:
6101
                $sg_mode = 0;
6102
                $prevent_reinit = 0;
6103
                break;
6104
        }
6105
        $this->prevent_reinit = $prevent_reinit;
6106
        $this->seriousgame_mode = $sg_mode;
6107
        $table = Database::get_course_table(TABLE_LP_MAIN);
6108
        $sql = "UPDATE $table SET
6109
                prevent_reinit = $prevent_reinit ,
6110
                seriousgame_mode = $sg_mode
6111
                WHERE iid = ".$this->get_id();
6112
        $res = Database::query($sql);
6113
        if ($res) {
6114
            return true;
6115
        } else {
6116
            return false;
6117
        }
6118
    }
6119
6120
    /**
6121
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
6122
     *
6123
     * @author ndiechburg <[email protected]>
6124
     */
6125
    public function switch_attempt_mode()
6126
    {
6127
        $mode = $this->get_attempt_mode();
6128
        switch ($mode) {
6129
            case 'single':
6130
                $next_mode = 'multiple';
6131
                break;
6132
            case 'multiple':
6133
                $next_mode = 'seriousgame';
6134
                break;
6135
            case 'seriousgame':
6136
            default:
6137
                $next_mode = 'single';
6138
                break;
6139
        }
6140
        $this->set_attempt_mode($next_mode);
6141
    }
6142
6143
    /**
6144
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
6145
     * but possibility to do again a completed item.
6146
     *
6147
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
6148
     *
6149
     * @author ndiechburg <[email protected]>
6150
     */
6151
    public function set_seriousgame_mode()
6152
    {
6153
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
6154
        $sql = "SELECT * FROM $lp_table
6155
                WHERE iid = ".$this->get_id();
6156
        $res = Database::query($sql);
6157
        if (Database::num_rows($res) > 0) {
6158
            $row = Database::fetch_array($res);
6159
            $force = $row['seriousgame_mode'];
6160
            if ($force == 1) {
6161
                $force = 0;
6162
            } elseif ($force == 0) {
6163
                $force = 1;
6164
            }
6165
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
6166
			        WHERE iid = ".$this->get_id();
6167
            Database::query($sql);
6168
            $this->seriousgame_mode = $force;
6169
6170
            return $force;
6171
        }
6172
6173
        return -1;
6174
    }
6175
6176
    /**
6177
     * Updates the "scorm_debug" value that shows or hide the debug window.
6178
     *
6179
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
6180
     */
6181
    public function update_scorm_debug()
6182
    {
6183
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
6184
        $sql = "SELECT * FROM $lp_table
6185
                WHERE iid = ".$this->get_id();
6186
        $res = Database::query($sql);
6187
        if (Database::num_rows($res) > 0) {
6188
            $row = Database::fetch_array($res);
6189
            $force = $row['debug'];
6190
            if ($force == 1) {
6191
                $force = 0;
6192
            } elseif ($force == 0) {
6193
                $force = 1;
6194
            }
6195
            $sql = "UPDATE $lp_table SET debug = $force
6196
                    WHERE iid = ".$this->get_id();
6197
            Database::query($sql);
6198
            $this->scorm_debug = $force;
6199
6200
            return $force;
6201
        }
6202
6203
        return -1;
6204
    }
6205
6206
    /**
6207
     * Function that makes a call to the function sort_tree_array and create_tree_array.
6208
     *
6209
     * @author Kevin Van Den Haute
6210
     *
6211
     * @param  array
6212
     */
6213
    public function tree_array($array)
6214
    {
6215
        $array = $this->sort_tree_array($array);
6216
        $this->create_tree_array($array);
6217
    }
6218
6219
    /**
6220
     * Creates an array with the elements of the learning path tree in it.
6221
     *
6222
     * @author Kevin Van Den Haute
6223
     *
6224
     * @param array $array
6225
     * @param int   $parent
6226
     * @param int   $depth
6227
     * @param array $tmp
6228
     */
6229
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
6230
    {
6231
        if (is_array($array)) {
6232
            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...
6233
                if ($array[$i]['parent_item_id'] == $parent) {
6234
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
6235
                        $tmp[] = $array[$i]['parent_item_id'];
6236
                        $depth++;
6237
                    }
6238
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
6239
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
6240
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
6241
6242
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
6243
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
6244
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
6245
                    $this->arrMenu[] = [
6246
                        'id' => $array[$i]['id'],
6247
                        'ref' => $ref,
6248
                        'item_type' => $array[$i]['item_type'],
6249
                        'title' => $array[$i]['title'],
6250
                        'title_raw' => $array[$i]['title_raw'],
6251
                        'path' => $path,
6252
                        'description' => $array[$i]['description'],
6253
                        'parent_item_id' => $array[$i]['parent_item_id'],
6254
                        'previous_item_id' => $array[$i]['previous_item_id'],
6255
                        'next_item_id' => $array[$i]['next_item_id'],
6256
                        'min_score' => $array[$i]['min_score'],
6257
                        'max_score' => $array[$i]['max_score'],
6258
                        'mastery_score' => $array[$i]['mastery_score'],
6259
                        'display_order' => $array[$i]['display_order'],
6260
                        'prerequisite' => $preq,
6261
                        'depth' => $depth,
6262
                        'audio' => $audio,
6263
                        'prerequisite_min_score' => $prerequisiteMinScore,
6264
                        'prerequisite_max_score' => $prerequisiteMaxScore,
6265
                    ];
6266
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
6267
                }
6268
            }
6269
        }
6270
    }
6271
6272
    /**
6273
     * Sorts a multi dimensional array by parent id and display order.
6274
     *
6275
     * @author Kevin Van Den Haute
6276
     *
6277
     * @param array $array (array with al the learning path items in it)
6278
     *
6279
     * @return array
6280
     */
6281
    public function sort_tree_array($array)
6282
    {
6283
        foreach ($array as $key => $row) {
6284
            $parent[$key] = $row['parent_item_id'];
6285
            $position[$key] = $row['display_order'];
6286
        }
6287
6288
        if (count($array) > 0) {
6289
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
6290
        }
6291
6292
        return $array;
6293
    }
6294
6295
    /**
6296
     * Function that creates a html list of learning path items so that we can add audio files to them.
6297
     *
6298
     * @author Kevin Van Den Haute
6299
     *
6300
     * @return string
6301
     */
6302
    public function overview()
6303
    {
6304
        $return = '';
6305
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
6306
6307
        // we need to start a form when we want to update all the mp3 files
6308
        if ($update_audio == 'true') {
6309
            $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">';
6310
        }
6311
        $return .= '<div id="message"></div>';
6312
        if (count($this->items) == 0) {
6313
            $return .= Display::return_message(get_lang('YouShouldAddItemsBeforeAttachAudio'), 'normal');
6314
        } else {
6315
            $return_audio = '<table class="table table-hover table-striped data_table">';
6316
            $return_audio .= '<tr>';
6317
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
6318
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
6319
            $return_audio .= '</tr>';
6320
6321
            if ($update_audio != 'true') {
6322
                $return .= '<div class="col-md-12">';
6323
                $return .= self::return_new_tree($update_audio);
6324
                $return .= '</div>';
6325
                $return .= Display::div(
6326
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
6327
                    ['style' => 'float:left; margin-top:15px;width:100%']
6328
                );
6329
            } else {
6330
                $return_audio .= self::return_new_tree($update_audio);
6331
                $return .= $return_audio.'</table>';
6332
            }
6333
6334
            // We need to close the form when we are updating the mp3 files.
6335
            if ($update_audio == 'true') {
6336
                $return .= '<div class="footer-audio">';
6337
                $return .= Display::button(
6338
                    'save_audio',
6339
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('SaveAudioAndOrganization'),
6340
                    ['class' => 'btn btn-primary', 'type' => 'submit']
6341
                );
6342
                $return .= '</div>';
6343
            }
6344
        }
6345
6346
        // We need to close the form when we are updating the mp3 files.
6347
        if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
6348
            $return .= '</form>';
6349
        }
6350
6351
        return $return;
6352
    }
6353
6354
    /**
6355
     * @param string $update_audio
6356
     *
6357
     * @return array
6358
     */
6359
    public function processBuildMenuElements($update_audio = 'false')
6360
    {
6361
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
6362
        $arrLP = $this->getItemsForForm();
6363
6364
        $this->tree_array($arrLP);
6365
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
6366
        unset($this->arrMenu);
6367
        $default_data = null;
6368
        $default_content = null;
6369
        $elements = [];
6370
        $return_audio = null;
6371
        $iconPath = api_get_path(SYS_CODE_PATH).'img/';
6372
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
6373
        $countItems = count($arrLP);
6374
6375
        $upIcon = Display::return_icon(
6376
            'up.png',
6377
            get_lang('Up'),
6378
            [],
6379
            ICON_SIZE_TINY
6380
        );
6381
6382
        $disableUpIcon = Display::return_icon(
6383
            'up_na.png',
6384
            get_lang('Up'),
6385
            [],
6386
            ICON_SIZE_TINY
6387
        );
6388
6389
        $downIcon = Display::return_icon(
6390
            'down.png',
6391
            get_lang('Down'),
6392
            [],
6393
            ICON_SIZE_TINY
6394
        );
6395
6396
        $disableDownIcon = Display::return_icon(
6397
            'down_na.png',
6398
            get_lang('Down'),
6399
            [],
6400
            ICON_SIZE_TINY
6401
        );
6402
6403
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
6404
6405
        $pluginCalendar = api_get_plugin_setting('learning_calendar', 'enabled') === 'true';
6406
        $plugin = null;
6407
        if ($pluginCalendar) {
6408
            $plugin = LearningCalendarPlugin::create();
6409
        }
6410
6411
        for ($i = 0; $i < $countItems; $i++) {
6412
            $parent_id = $arrLP[$i]['parent_item_id'];
6413
            $title = $arrLP[$i]['title'];
6414
            $title_cut = $arrLP[$i]['title_raw'];
6415
            if ($show === false) {
6416
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
6417
            }
6418
            // Link for the documents
6419
            if ($arrLP[$i]['item_type'] === 'document' || $arrLP[$i]['item_type'] == TOOL_READOUT_TEXT) {
6420
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6421
                $title_cut = Display::url(
6422
                    $title_cut,
6423
                    $url,
6424
                    [
6425
                        'class' => 'ajax moved',
6426
                        'data-title' => $title,
6427
                        'title' => $title,
6428
                    ]
6429
                );
6430
            }
6431
6432
            // Detect if type is FINAL_ITEM to set path_id to SESSION
6433
            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6434
                Session::write('pathItem', $arrLP[$i]['path']);
6435
            }
6436
6437
            $oddClass = 'row_even';
6438
            if (($i % 2) == 0) {
6439
                $oddClass = 'row_odd';
6440
            }
6441
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
6442
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
6443
6444
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
6445
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
6446
            } else {
6447
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
6448
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
6449
                } else {
6450
                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
6451
                        $icon = Display::return_icon('flag_checkered.png');
6452
                    } elseif (TOOL_XAPI === $arrLP[$i]['item_type']) {
6453
                        $icon = Display::return_icon('import_scorm.png');
6454
                    } elseif (TOOL_H5P) {
6455
                        $icon = Display::return_icon('plugin_h5p_import.png');
6456
                    } elseif (TOOL_SURVEY === $arrLP[$i]['item_type']) {
6457
                        $icon = Display::return_icon('survey.gif');
6458
                    } else {
6459
                        $icon = Display::return_icon('folder_document.gif');
6460
                    }
6461
                }
6462
            }
6463
6464
            // The audio column.
6465
            $return_audio .= '<td align="left" style="padding-left:10px;">';
6466
            $audio = '';
6467
            if (!$update_audio || $update_audio != 'true') {
6468
                if (empty($arrLP[$i]['audio'])) {
6469
                    $audio .= '';
6470
                }
6471
            } else {
6472
                $types = self::getChapterTypes();
6473
                if (!in_array($arrLP[$i]['item_type'], $types)) {
6474
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
6475
                    if (!empty($arrLP[$i]['audio'])) {
6476
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
6477
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('RemoveAudio');
6478
                    }
6479
                }
6480
            }
6481
6482
            $return_audio .= Display::span($icon.' '.$title).
6483
                Display::tag(
6484
                    'td',
6485
                    $audio,
6486
                    ['style' => '']
6487
                );
6488
            $return_audio .= '</td>';
6489
            $move_icon = '';
6490
            $move_item_icon = '';
6491
            $edit_icon = '';
6492
            $delete_icon = '';
6493
            $audio_icon = '';
6494
            $prerequisities_icon = '';
6495
            $forumIcon = '';
6496
            $previewIcon = '';
6497
            $pluginCalendarIcon = '';
6498
            $orderIcons = '';
6499
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
6500
6501
            if ($is_allowed_to_edit) {
6502
                if (!$update_audio || $update_audio != 'true') {
6503
                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
6504
                        $move_icon .= '<a class="moved" href="#">';
6505
                        $move_icon .= Display::return_icon(
6506
                            'move_everywhere.png',
6507
                            get_lang('Move'),
6508
                            [],
6509
                            ICON_SIZE_TINY
6510
                        );
6511
                        $move_icon .= '</a>';
6512
                    }
6513
                }
6514
6515
                // No edit for this item types
6516
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6517
                    if ($arrLP[$i]['item_type'] != 'dir') {
6518
                        $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">';
6519
                        $edit_icon .= Display::return_icon(
6520
                            'edit.png',
6521
                            get_lang('LearnpathEditModule'),
6522
                            [],
6523
                            ICON_SIZE_TINY
6524
                        );
6525
                        $edit_icon .= '</a>';
6526
6527
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6528
                            $forumThread = null;
6529
                            if (isset($this->items[$arrLP[$i]['id']])) {
6530
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6531
                                    $this->course_int_id,
6532
                                    $this->lp_session_id
6533
                                );
6534
                            }
6535
                            if ($forumThread) {
6536
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6537
                                        'action' => 'dissociate_forum',
6538
                                        'id' => $arrLP[$i]['id'],
6539
                                        'lp_id' => $this->lp_id,
6540
                                    ]);
6541
                                $forumIcon = Display::url(
6542
                                    Display::return_icon(
6543
                                        'forum.png',
6544
                                        get_lang('DissociateForumToLPItem'),
6545
                                        [],
6546
                                        ICON_SIZE_TINY
6547
                                    ),
6548
                                    $forumIconUrl,
6549
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6550
                                );
6551
                            } else {
6552
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6553
                                        'action' => 'create_forum',
6554
                                        'id' => $arrLP[$i]['id'],
6555
                                        'lp_id' => $this->lp_id,
6556
                                    ]);
6557
                                $forumIcon = Display::url(
6558
                                    Display::return_icon(
6559
                                        'forum.png',
6560
                                        get_lang('AssociateForumToLPItem'),
6561
                                        [],
6562
                                        ICON_SIZE_TINY
6563
                                    ),
6564
                                    $forumIconUrl,
6565
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6566
                                );
6567
                            }
6568
                        }
6569
                    } else {
6570
                        $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">';
6571
                        $edit_icon .= Display::return_icon(
6572
                            'edit.png',
6573
                            get_lang('LearnpathEditModule'),
6574
                            [],
6575
                            ICON_SIZE_TINY
6576
                        );
6577
                        $edit_icon .= '</a>';
6578
                    }
6579
                } else {
6580
                    if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6581
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6582
                        $edit_icon .= Display::return_icon(
6583
                            'edit.png',
6584
                            get_lang('Edit'),
6585
                            [],
6586
                            ICON_SIZE_TINY
6587
                        );
6588
                        $edit_icon .= '</a>';
6589
                    }
6590
                }
6591
6592
                if ($pluginCalendar) {
6593
                    $pluginLink = $pluginUrl.
6594
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6595
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6596
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6597
                    if ($itemInfo && $itemInfo['value'] == 1) {
6598
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6599
                    }
6600
                    $pluginCalendarIcon = Display::url(
6601
                        $iconCalendar,
6602
                        $pluginLink,
6603
                        ['class' => 'btn btn-default']
6604
                    );
6605
                }
6606
6607
                if ($arrLP[$i]['item_type'] != 'final_item') {
6608
                    $orderIcons = Display::url(
6609
                        $upIcon,
6610
                        'javascript:void(0)',
6611
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
6612
                    );
6613
                    $orderIcons .= Display::url(
6614
                        $downIcon,
6615
                        'javascript:void(0)',
6616
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
6617
                    );
6618
                }
6619
6620
                $delete_icon .= ' <a
6621
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
6622
                    onclick="return confirmation(\''.addslashes($title).'\');"
6623
                    class="btn btn-default">';
6624
                $delete_icon .= Display::return_icon(
6625
                    'delete.png',
6626
                    get_lang('LearnpathDeleteModule'),
6627
                    [],
6628
                    ICON_SIZE_TINY
6629
                );
6630
                $delete_icon .= '</a>';
6631
6632
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6633
                $previewImage = Display::return_icon(
6634
                    'preview_view.png',
6635
                    get_lang('Preview'),
6636
                    [],
6637
                    ICON_SIZE_TINY
6638
                );
6639
6640
                switch ($arrLP[$i]['item_type']) {
6641
                    case TOOL_DOCUMENT:
6642
                    case TOOL_LP_FINAL_ITEM:
6643
                    case TOOL_READOUT_TEXT:
6644
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6645
                        $previewIcon = Display::url(
6646
                            $previewImage,
6647
                            $urlPreviewLink,
6648
                            [
6649
                                'target' => '_blank',
6650
                                'class' => 'btn btn-default',
6651
                                'data-title' => $arrLP[$i]['title'],
6652
                                'title' => $arrLP[$i]['title'],
6653
                            ]
6654
                        );
6655
                        break;
6656
                    case TOOL_THREAD:
6657
                    case TOOL_FORUM:
6658
                    case TOOL_QUIZ:
6659
                    case TOOL_STUDENTPUBLICATION:
6660
                    case TOOL_LP_FINAL_ITEM:
6661
                    case TOOL_LINK:
6662
                        $class = 'btn btn-default';
6663
                        $target = '_blank';
6664
                        $link = self::rl_get_resource_link_for_learnpath(
6665
                            $this->course_int_id,
6666
                            $this->lp_id,
6667
                            $arrLP[$i]['id'],
6668
                            0
6669
                        );
6670
                        $previewIcon = Display::url(
6671
                            $previewImage,
6672
                            $link,
6673
                            [
6674
                                'class' => $class,
6675
                                'data-title' => $arrLP[$i]['title'],
6676
                                'title' => $arrLP[$i]['title'],
6677
                                'target' => $target,
6678
                            ]
6679
                        );
6680
                        break;
6681
                    default:
6682
                        $previewIcon = Display::url(
6683
                            $previewImage,
6684
                            $url.'&action=view_item',
6685
                            ['class' => 'btn btn-default', 'target' => '_blank']
6686
                        );
6687
                        break;
6688
                }
6689
6690
                if ($arrLP[$i]['item_type'] != 'dir') {
6691
                    $prerequisities_icon = Display::url(
6692
                        Display::return_icon(
6693
                            'accept.png',
6694
                            get_lang('LearnpathPrerequisites'),
6695
                            [],
6696
                            ICON_SIZE_TINY
6697
                        ),
6698
                        $url.'&action=edit_item_prereq',
6699
                        ['class' => 'btn btn-default']
6700
                    );
6701
                    if ($arrLP[$i]['item_type'] != 'final_item') {
6702
                        $move_item_icon = Display::url(
6703
                            Display::return_icon(
6704
                                'move.png',
6705
                                get_lang('Move'),
6706
                                [],
6707
                                ICON_SIZE_TINY
6708
                            ),
6709
                            $url.'&action=move_item',
6710
                            ['class' => 'btn btn-default']
6711
                        );
6712
                    }
6713
                    $audio_icon = Display::url(
6714
                        Display::return_icon(
6715
                            'audio.png',
6716
                            get_lang('UplUpload'),
6717
                            [],
6718
                            ICON_SIZE_TINY
6719
                        ),
6720
                        $url.'&action=add_audio',
6721
                        ['class' => 'btn btn-default']
6722
                    );
6723
                }
6724
            }
6725
            if ($update_audio != 'true') {
6726
                $row = $move_icon.' '.$icon.
6727
                    Display::span($title_cut).
6728
                    Display::tag(
6729
                        'div',
6730
                        "<div class=\"btn-group btn-group-xs\">
6731
                                    $previewIcon
6732
                                    $audio
6733
                                    $edit_icon
6734
                                    $pluginCalendarIcon
6735
                                    $forumIcon
6736
                                    $prerequisities_icon
6737
                                    $move_item_icon
6738
                                    $audio_icon
6739
                                    $orderIcons
6740
                                    $delete_icon
6741
                                </div>",
6742
                        ['class' => 'btn-toolbar button_actions']
6743
                    );
6744
            } else {
6745
                $row =
6746
                    Display::span($title.$icon).
6747
                    Display::span($audio, ['class' => 'button_actions']);
6748
            }
6749
6750
            $default_data[$arrLP[$i]['id']] = $row;
6751
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6752
6753
            if (empty($parent_id)) {
6754
                $elements[$arrLP[$i]['id']]['data'] = $row;
6755
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6756
            } else {
6757
                $parent_arrays = [];
6758
                if ($arrLP[$i]['depth'] > 1) {
6759
                    // Getting list of parents
6760
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6761
                        foreach ($arrLP as $item) {
6762
                            if ($item['id'] == $parent_id) {
6763
                                if ($item['parent_item_id'] == 0) {
6764
                                    $parent_id = $item['id'];
6765
                                    break;
6766
                                } else {
6767
                                    $parent_id = $item['parent_item_id'];
6768
                                    if (empty($parent_arrays)) {
6769
                                        $parent_arrays[] = intval($item['id']);
6770
                                    }
6771
                                    $parent_arrays[] = $parent_id;
6772
                                    break;
6773
                                }
6774
                            }
6775
                        }
6776
                    }
6777
                }
6778
6779
                if (!empty($parent_arrays)) {
6780
                    $parent_arrays = array_reverse($parent_arrays);
6781
                    $val = '$elements';
6782
                    $x = 0;
6783
                    foreach ($parent_arrays as $item) {
6784
                        if ($x != count($parent_arrays) - 1) {
6785
                            $val .= '["'.$item.'"]["children"]';
6786
                        } else {
6787
                            $val .= '["'.$item.'"]["children"]';
6788
                        }
6789
                        $x++;
6790
                    }
6791
                    $val .= "";
6792
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6793
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6794
                } else {
6795
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6796
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6797
                }
6798
            }
6799
        }
6800
6801
        return [
6802
            'elements' => $elements,
6803
            'default_data' => $default_data,
6804
            'default_content' => $default_content,
6805
            'return_audio' => $return_audio,
6806
        ];
6807
    }
6808
6809
    /**
6810
     * @param string $updateAudio true/false strings
6811
     *
6812
     * @return string
6813
     */
6814
    public function returnLpItemList($updateAudio)
6815
    {
6816
        $result = $this->processBuildMenuElements($updateAudio);
6817
6818
        $html = self::print_recursive(
6819
            $result['elements'],
6820
            $result['default_data'],
6821
            $result['default_content']
6822
        );
6823
6824
        if (!empty($html)) {
6825
            $html .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6826
        }
6827
6828
        return $html;
6829
    }
6830
6831
    /**
6832
     * @param string $update_audio
6833
     * @param bool   $drop_element_here
6834
     *
6835
     * @return string
6836
     */
6837
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6838
    {
6839
        $result = $this->processBuildMenuElements($update_audio);
6840
6841
        $list = '<ul id="lp_item_list">';
6842
        $tree = $this->print_recursive(
6843
            $result['elements'],
6844
            $result['default_data'],
6845
            $result['default_content']
6846
        );
6847
6848
        if (!empty($tree)) {
6849
            $list .= $tree;
6850
        } else {
6851
            if ($drop_element_here) {
6852
                $list .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6853
            }
6854
        }
6855
        $list .= '</ul>';
6856
6857
        $return = Display::panelCollapse(
6858
            $this->getNameNoTags(),
6859
            $list,
6860
            'scorm-list',
6861
            null,
6862
            'scorm-list-accordion',
6863
            'scorm-list-collapse'
6864
        );
6865
6866
        if ($update_audio === 'true') {
6867
            $return = $result['return_audio'];
6868
        }
6869
6870
        return $return;
6871
    }
6872
6873
    /**
6874
     * @param array $elements
6875
     * @param array $default_data
6876
     * @param array $default_content
6877
     *
6878
     * @return string
6879
     */
6880
    public function print_recursive($elements, $default_data, $default_content)
6881
    {
6882
        $return = '';
6883
        foreach ($elements as $key => $item) {
6884
            if (TOOL_LP_FINAL_ITEM === $item['type']) {
6885
                $key = 'final_item';
6886
            }
6887
            if (isset($item['load_data']) || empty($item['data'])) {
6888
                $item['data'] = $default_data[$item['load_data']];
6889
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6890
            }
6891
            $sub_list = '';
6892
            if (isset($item['type']) && $item['type'] === 'dir') {
6893
                // empty value
6894
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6895
            }
6896
            if (empty($item['children'])) {
6897
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6898
                $active = null;
6899
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6900
                    $active = 'active';
6901
                }
6902
                $return .= Display::tag(
6903
                    'li',
6904
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6905
                    ['id' => $key, 'class' => 'record li_container']
6906
                );
6907
            } else {
6908
                // Sections
6909
                $data = '';
6910
                if (isset($item['children'])) {
6911
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6912
                }
6913
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6914
                $return .= Display::tag(
6915
                    'li',
6916
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6917
                    ['id' => $key, 'class' => 'record li_container']
6918
                );
6919
            }
6920
        }
6921
6922
        return $return;
6923
    }
6924
6925
    /**
6926
     * This function builds the action menu.
6927
     *
6928
     * @param bool   $returnString           Optional
6929
     * @param bool   $showRequirementButtons Optional. Allow show the requirements button
6930
     * @param bool   $isConfigPage           Optional. If is the config page, show the edit button
6931
     * @param bool   $allowExpand            Optional. Allow show the expand/contract button
6932
     * @param string $action
6933
     * @param array  $extraField
6934
     *
6935
     * @return string
6936
     */
6937
    public function build_action_menu(
6938
        $returnString = false,
6939
        $showRequirementButtons = true,
6940
        $isConfigPage = false,
6941
        $allowExpand = true,
6942
        $action = '',
6943
        $extraField = [],
6944
        $noEdition = false
6945
    ) {
6946
        $actionsRight = '';
6947
        $lpId = $this->lp_id;
6948
        if (!isset($extraField['backTo']) && empty($extraField['backTo'])) {
6949
            $back = Display::url(
6950
                Display::return_icon(
6951
                    'back.png',
6952
                    get_lang('ReturnToLearningPaths'),
6953
                    '',
6954
                    ICON_SIZE_MEDIUM
6955
                ),
6956
                'lp_controller.php?'.api_get_cidreq()
6957
            );
6958
        } else {
6959
            $back = Display::url(
6960
                Display::return_icon(
6961
                    'back.png',
6962
                    get_lang('Back'),
6963
                    '',
6964
                    ICON_SIZE_MEDIUM
6965
                ),
6966
                $extraField['backTo']
6967
            );
6968
        }
6969
6970
        $actionsLeft = $back;
6971
        $actionsLeft .= Display::url(
6972
            Display::return_icon(
6973
                'preview_view.png',
6974
                get_lang('Preview'),
6975
                '',
6976
                ICON_SIZE_MEDIUM
6977
            ),
6978
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6979
                'action' => 'view',
6980
                'lp_id' => $lpId,
6981
                'isStudentView' => 'true',
6982
            ])
6983
        );
6984
6985
        if (!$noEdition) {
6986
            $actionsLeft .= Display::url(
6987
                Display::return_icon(
6988
                    'upload_audio.png',
6989
                    get_lang('UpdateAllAudioFragments'),
6990
                    '',
6991
                    ICON_SIZE_MEDIUM
6992
                ),
6993
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6994
                    'action' => 'admin_view',
6995
                    'lp_id' => $lpId,
6996
                    'updateaudio' => 'true',
6997
                ])
6998
            );
6999
        }
7000
7001
        $subscriptionSettings = self::getSubscriptionSettings();
7002
        $request = api_request_uri();
7003
        if ((strpos($request, 'edit') === false) && !$noEdition) {
7004
            $actionsLeft .= Display::url(
7005
                Display::return_icon(
7006
                    'settings.png',
7007
                    get_lang('CourseSettings'),
7008
                    '',
7009
                    ICON_SIZE_MEDIUM
7010
                ),
7011
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
7012
                    'action' => 'edit',
7013
                    'lp_id' => $lpId,
7014
                ])
7015
            );
7016
        }
7017
7018
        if ((strpos($request, 'build') === false && !$noEdition &&
7019
            strpos($request, 'add_item') === false) ||
7020
            in_array($action, ['add_audio'])
7021
        ) {
7022
            $actionsLeft .= Display::url(
7023
                Display::return_icon(
7024
                    'edit.png',
7025
                    get_lang('Edit'),
7026
                    '',
7027
                    ICON_SIZE_MEDIUM
7028
                ),
7029
                'lp_controller.php?'.http_build_query([
7030
                    'action' => 'build',
7031
                    'lp_id' => $lpId,
7032
                ]).'&'.api_get_cidreq()
7033
            );
7034
        }
7035
7036
        if (strpos(api_get_self(), 'lp_subscribe_users.php') === false) {
7037
            if ($this->subscribeUsers == 1 &&
7038
                $subscriptionSettings['allow_add_users_to_lp']) {
7039
                $actionsLeft .= Display::url(
7040
                    Display::return_icon(
7041
                        'user.png',
7042
                        get_lang('SubscribeUsersToLp'),
7043
                        '',
7044
                        ICON_SIZE_MEDIUM
7045
                    ),
7046
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=".$lpId."&".api_get_cidreq()
7047
                );
7048
            }
7049
        }
7050
7051
        if ($allowExpand) {
7052
            $actionsLeft .= Display::url(
7053
                Display::return_icon(
7054
                    'expand.png',
7055
                    get_lang('Expand'),
7056
                    ['id' => 'expand'],
7057
                    ICON_SIZE_MEDIUM
7058
                ).
7059
                Display::return_icon(
7060
                    'contract.png',
7061
                    get_lang('Collapse'),
7062
                    ['id' => 'contract', 'class' => 'hide'],
7063
                    ICON_SIZE_MEDIUM
7064
                ),
7065
                '#',
7066
                ['role' => 'button', 'id' => 'hide_bar_template']
7067
            );
7068
        }
7069
7070
        if ($showRequirementButtons) {
7071
            $buttons = [
7072
                [
7073
                    'title' => get_lang('SetPrerequisiteForEachItem'),
7074
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
7075
                        'action' => 'set_previous_step_as_prerequisite',
7076
                        'lp_id' => $lpId,
7077
                    ]),
7078
                ],
7079
                [
7080
                    'title' => get_lang('ClearAllPrerequisites'),
7081
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
7082
                        'action' => 'clear_prerequisites',
7083
                        'lp_id' => $lpId,
7084
                    ]),
7085
                ],
7086
            ];
7087
            $actionsRight = Display::groupButtonWithDropDown(
7088
                get_lang('PrerequisitesOptions'),
7089
                $buttons,
7090
                true
7091
            );
7092
        }
7093
7094
        if (api_is_platform_admin() && isset($extraField['authorlp'])) {
7095
            $actionsLeft .= Display::url(
7096
                Display::return_icon(
7097
                    'add-groups.png',
7098
                    get_lang('Author'),
7099
                    '',
7100
                    ICON_SIZE_MEDIUM
7101
                ),
7102
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
7103
                    'action' => 'author_view',
7104
                    'lp_id' => $lpId,
7105
                ])
7106
            );
7107
        }
7108
7109
        $toolbar = Display::toolbarAction(
7110
            'actions-lp-controller',
7111
            [$actionsLeft, $actionsRight]
7112
        );
7113
7114
        if ($returnString) {
7115
            return $toolbar;
7116
        }
7117
7118
        echo $toolbar;
7119
    }
7120
7121
    /**
7122
     * Creates the default learning path folder.
7123
     *
7124
     * @param array $course
7125
     * @param int   $creatorId
7126
     *
7127
     * @return bool
7128
     */
7129
    public static function generate_learning_path_folder($course, $creatorId = 0)
7130
    {
7131
        // Creating learning_path folder
7132
        $dir = '/learning_path';
7133
        $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
7134
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
7135
7136
        $folder = false;
7137
        if (!is_dir($filepath.'/'.$dir)) {
7138
            $folderData = create_unexisting_directory(
7139
                $course,
7140
                $creatorId,
7141
                0,
7142
                null,
7143
                0,
7144
                $filepath,
7145
                $dir,
7146
                get_lang('LearningPaths'),
7147
                0
7148
            );
7149
            if (!empty($folderData)) {
7150
                $folder = true;
7151
            }
7152
        } else {
7153
            $folder = true;
7154
        }
7155
7156
        return $folder;
7157
    }
7158
7159
    /**
7160
     * @param array  $course
7161
     * @param string $lp_name
7162
     * @param int    $creatorId
7163
     *
7164
     * @return array
7165
     */
7166
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
7167
    {
7168
        $filepath = '';
7169
        $dir = '/learning_path/';
7170
7171
        if (empty($lp_name)) {
7172
            $lp_name = $this->name;
7173
        }
7174
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
7175
        $folder = self::generate_learning_path_folder($course, $creatorId);
7176
7177
        // Limits title size
7178
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
7179
        $dir = $dir.$title;
7180
7181
        // Creating LP folder
7182
        $documentId = null;
7183
        if ($folder) {
7184
            $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
7185
            if (!is_dir($filepath.'/'.$dir)) {
7186
                $folderData = create_unexisting_directory(
7187
                    $course,
7188
                    $creatorId,
7189
                    0,
7190
                    0,
7191
                    0,
7192
                    $filepath,
7193
                    $dir,
7194
                    $lp_name
7195
                );
7196
                if (!empty($folderData)) {
7197
                    $folder = true;
7198
                }
7199
7200
                $documentId = $folderData['id'];
7201
            } else {
7202
                $folder = true;
7203
            }
7204
            $dir = $dir.'/';
7205
            if ($folder) {
7206
                $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
7207
            }
7208
        }
7209
7210
        if (empty($documentId)) {
7211
            $dir = api_remove_trailing_slash($dir);
7212
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
7213
        }
7214
7215
        $array = [
7216
            'dir' => $dir,
7217
            'filepath' => $filepath,
7218
            'folder' => $folder,
7219
            'id' => $documentId,
7220
        ];
7221
7222
        return $array;
7223
    }
7224
7225
    /**
7226
     * Create a new document //still needs some finetuning.
7227
     *
7228
     * @param array  $courseInfo
7229
     * @param string $content
7230
     * @param string $title
7231
     * @param string $extension
7232
     * @param int    $parentId
7233
     * @param int    $creatorId  creator id
7234
     *
7235
     * @return int
7236
     */
7237
    public function create_document(
7238
        $courseInfo,
7239
        $content = '',
7240
        $title = '',
7241
        $extension = 'html',
7242
        $parentId = 0,
7243
        $creatorId = 0
7244
    ) {
7245
        if (!empty($courseInfo)) {
7246
            $course_id = $courseInfo['real_id'];
7247
        } else {
7248
            $course_id = api_get_course_int_id();
7249
        }
7250
7251
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
7252
        $sessionId = api_get_session_id();
7253
7254
        // Generates folder
7255
        $result = $this->generate_lp_folder($courseInfo);
7256
        $dir = $result['dir'];
7257
7258
        if (empty($parentId) || $parentId == '/') {
7259
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
7260
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
7261
7262
            if ($parentId === '/') {
7263
                $dir = '/';
7264
            }
7265
7266
            // Please, do not modify this dirname formatting.
7267
            if (strstr($dir, '..')) {
7268
                $dir = '/';
7269
            }
7270
7271
            if (!empty($dir[0]) && $dir[0] == '.') {
7272
                $dir = substr($dir, 1);
7273
            }
7274
            if (!empty($dir[0]) && $dir[0] != '/') {
7275
                $dir = '/'.$dir;
7276
            }
7277
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7278
                $dir .= '/';
7279
            }
7280
        } else {
7281
            $parentInfo = DocumentManager::get_document_data_by_id(
7282
                $parentId,
7283
                $courseInfo['code']
7284
            );
7285
            if (!empty($parentInfo)) {
7286
                $dir = $parentInfo['path'].'/';
7287
            }
7288
        }
7289
7290
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7291
        if (!is_dir($filepath)) {
7292
            $dir = '/';
7293
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7294
        }
7295
7296
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
7297
        // is already escaped twice when it gets here.
7298
        $originalTitle = !empty($title) ? $title : $_POST['title'];
7299
        if (!empty($title)) {
7300
            $title = api_replace_dangerous_char(stripslashes($title));
7301
        } else {
7302
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
7303
        }
7304
7305
        $title = disable_dangerous_file($title);
7306
        $filename = $title;
7307
        $content = !empty($content) ? $content : $_POST['content_lp'];
7308
        $tmp_filename = $filename;
7309
7310
        $i = 0;
7311
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
7312
            $tmp_filename = $filename.'_'.++$i;
7313
        }
7314
7315
        $filename = $tmp_filename.'.'.$extension;
7316
        if ($extension == 'html') {
7317
            $content = stripslashes($content);
7318
            $content = str_replace(
7319
                api_get_path(WEB_COURSE_PATH),
7320
                api_get_path(REL_PATH).'courses/',
7321
                $content
7322
            );
7323
7324
            // Change the path of mp3 to absolute.
7325
            // The first regexp deals with :// urls.
7326
            $content = preg_replace(
7327
                "|(flashvars=\"file=)([^:/]+)/|",
7328
                "$1".api_get_path(
7329
                    REL_COURSE_PATH
7330
                ).$courseInfo['path'].'/document/',
7331
                $content
7332
            );
7333
            // The second regexp deals with audio/ urls.
7334
            $content = preg_replace(
7335
                "|(flashvars=\"file=)([^/]+)/|",
7336
                "$1".api_get_path(
7337
                    REL_COURSE_PATH
7338
                ).$courseInfo['path'].'/document/$2/',
7339
                $content
7340
            );
7341
            // For flv player: To prevent edition problem with firefox,
7342
            // we have to use a strange tip (don't blame me please).
7343
            $content = str_replace(
7344
                '</body>',
7345
                '<style type="text/css">body{}</style></body>',
7346
                $content
7347
            );
7348
        }
7349
7350
        if (!file_exists($filepath.$filename)) {
7351
            if ($fp = @fopen($filepath.$filename, 'w')) {
7352
                fputs($fp, $content);
7353
                fclose($fp);
7354
7355
                $file_size = filesize($filepath.$filename);
7356
                $save_file_path = $dir.$filename;
7357
7358
                $document_id = add_document(
7359
                    $courseInfo,
7360
                    $save_file_path,
7361
                    'file',
7362
                    $file_size,
7363
                    $tmp_filename,
7364
                    '',
7365
                    0, //readonly
7366
                    true,
7367
                    null,
7368
                    $sessionId,
7369
                    $creatorId
7370
                );
7371
7372
                if ($document_id) {
7373
                    api_item_property_update(
7374
                        $courseInfo,
7375
                        TOOL_DOCUMENT,
7376
                        $document_id,
7377
                        'DocumentAdded',
7378
                        $creatorId,
7379
                        null,
7380
                        null,
7381
                        null,
7382
                        null,
7383
                        $sessionId
7384
                    );
7385
7386
                    $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7387
                    $new_title = $originalTitle;
7388
7389
                    if ($new_comment || $new_title) {
7390
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7391
                        $ct = '';
7392
                        if ($new_comment) {
7393
                            $ct .= ", comment='".Database::escape_string($new_comment)."'";
7394
                        }
7395
                        if ($new_title) {
7396
                            $ct .= ", title='".Database::escape_string($new_title)."' ";
7397
                        }
7398
7399
                        $sql = "UPDATE ".$tbl_doc." SET ".substr($ct, 1)."
7400
                               WHERE c_id = ".$course_id." AND id = ".$document_id;
7401
                        Database::query($sql);
7402
                    }
7403
                }
7404
7405
                return $document_id;
7406
            }
7407
        }
7408
    }
7409
7410
    /**
7411
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
7412
     *
7413
     * @param array $_course array
7414
     */
7415
    public function edit_document($_course)
7416
    {
7417
        $course_id = api_get_course_int_id();
7418
        $urlAppend = api_get_configuration_value('url_append');
7419
        // Please, do not modify this dirname formatting.
7420
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
7421
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
7422
7423
        if (strstr($dir, '..')) {
7424
            $dir = '/';
7425
        }
7426
7427
        if (isset($dir[0]) && $dir[0] == '.') {
7428
            $dir = substr($dir, 1);
7429
        }
7430
7431
        if (isset($dir[0]) && $dir[0] != '/') {
7432
            $dir = '/'.$dir;
7433
        }
7434
7435
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7436
            $dir .= '/';
7437
        }
7438
7439
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
7440
        if (!is_dir($filepath)) {
7441
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
7442
        }
7443
7444
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
7445
7446
        if (isset($_POST['path']) && !empty($_POST['path'])) {
7447
            $document_id = (int) $_POST['path'];
7448
            $documentInfo = DocumentManager::get_document_data_by_id($document_id, api_get_course_id(), false, null, true);
7449
            if (empty($documentInfo)) {
7450
                // Try with iid
7451
                $table = Database::get_course_table(TABLE_DOCUMENT);
7452
                $sql = "SELECT id, path FROM $table
7453
                        WHERE c_id = $course_id AND iid = $document_id AND path NOT LIKE '%_DELETED_%' ";
7454
                $res_doc = Database::query($sql);
7455
                $row = Database::fetch_array($res_doc);
7456
                if ($row) {
7457
                    $document_id = $row['id'];
7458
                    $documentPath = $row['path'];
7459
                }
7460
            } else {
7461
                $documentPath = $documentInfo['path'];
7462
            }
7463
7464
            $content = stripslashes($_POST['content_lp']);
7465
            $file = $filepath.$documentPath;
7466
7467
            if (!file_exists($file)) {
7468
                return false;
7469
            }
7470
7471
            if ($fp = @fopen($file, 'w')) {
7472
                $content = str_replace(
7473
                    api_get_path(WEB_COURSE_PATH),
7474
                    $urlAppend.api_get_path(REL_COURSE_PATH),
7475
                    $content
7476
                );
7477
                // Change the path of mp3 to absolute.
7478
                // The first regexp deals with :// urls.
7479
                $content = preg_replace(
7480
                    "|(flashvars=\"file=)([^:/]+)/|",
7481
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
7482
                    $content
7483
                );
7484
                // The second regexp deals with audio/ urls.
7485
                $content = preg_replace(
7486
                    "|(flashvars=\"file=)([^:/]+)/|",
7487
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
7488
                    $content
7489
                );
7490
                fputs($fp, $content);
7491
                fclose($fp);
7492
7493
                $sql = "UPDATE $table_doc SET
7494
                            title='".Database::escape_string($_POST['title'])."'
7495
                        WHERE c_id = $course_id AND id = ".$document_id;
7496
                Database::query($sql);
7497
            }
7498
        }
7499
    }
7500
7501
    /**
7502
     * Displays the selected item, with a panel for manipulating the item.
7503
     *
7504
     * @param int    $item_id
7505
     * @param string $msg
7506
     * @param bool   $show_actions
7507
     *
7508
     * @return string
7509
     */
7510
    public function display_item($item_id, $msg = null, $show_actions = true)
7511
    {
7512
        $course_id = api_get_course_int_id();
7513
        $return = '';
7514
        if (is_numeric($item_id)) {
7515
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7516
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
7517
                    WHERE lp.iid = ".intval($item_id);
7518
            $result = Database::query($sql);
7519
            while ($row = Database::fetch_array($result, 'ASSOC')) {
7520
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
7521
7522
                // Prevents wrong parent selection for document, see Bug#1251.
7523
                if ($row['item_type'] != 'dir') {
7524
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
7525
                }
7526
7527
                if ($show_actions) {
7528
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7529
                }
7530
                $return .= '<div style="padding:10px;">';
7531
7532
                if ($msg != '') {
7533
                    $return .= $msg;
7534
                }
7535
7536
                $return .= '<h3>'.$row['title'].'</h3>';
7537
7538
                switch ($row['item_type']) {
7539
                    case TOOL_THREAD:
7540
                        $link = $this->rl_get_resource_link_for_learnpath(
7541
                            $course_id,
7542
                            $row['lp_id'],
7543
                            $item_id,
7544
                            0
7545
                        );
7546
                        $return .= Display::url(
7547
                            get_lang('GoToThread'),
7548
                            $link,
7549
                            ['class' => 'btn btn-primary']
7550
                        );
7551
                        break;
7552
                    case TOOL_FORUM:
7553
                        $return .= Display::url(
7554
                            get_lang('GoToForum'),
7555
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
7556
                            ['class' => 'btn btn-primary']
7557
                        );
7558
                        break;
7559
                    case TOOL_QUIZ:
7560
                        if (!empty($row['path'])) {
7561
                            $exercise = new Exercise();
7562
                            $exercise->read($row['path']);
7563
                            $return .= $exercise->description.'<br />';
7564
                            $return .= Display::url(
7565
                                get_lang('GoToExercise'),
7566
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
7567
                                ['class' => 'btn btn-primary']
7568
                            );
7569
                        }
7570
                        break;
7571
                    case TOOL_LP_FINAL_ITEM:
7572
                        $return .= $this->getSavedFinalItem();
7573
                        break;
7574
                    case TOOL_DOCUMENT:
7575
                    case TOOL_READOUT_TEXT:
7576
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7577
                        $sql_doc = "SELECT path FROM $tbl_doc
7578
                                    WHERE c_id = $course_id AND iid = ".intval($row['path']);
7579
                        $result = Database::query($sql_doc);
7580
                        $path_file = Database::result($result, 0, 0);
7581
                        $path_parts = pathinfo($path_file);
7582
                        // TODO: Correct the following naive comparisons.
7583
                        if (in_array($path_parts['extension'], [
7584
                            'html',
7585
                            'txt',
7586
                            'png',
7587
                            'jpg',
7588
                            'JPG',
7589
                            'jpeg',
7590
                            'JPEG',
7591
                            'gif',
7592
                            'swf',
7593
                            'pdf',
7594
                            'htm',
7595
                        ])) {
7596
                            $return .= $this->display_document($row['path'], true, true);
7597
                        }
7598
                        break;
7599
                    case TOOL_HOTPOTATOES:
7600
                        $return .= $this->display_document($row['path'], false, true);
7601
                        break;
7602
                }
7603
                $return .= '</div>';
7604
            }
7605
        }
7606
7607
        return $return;
7608
    }
7609
7610
    /**
7611
     * Shows the needed forms for editing a specific item.
7612
     *
7613
     * @param int $item_id
7614
     *
7615
     * @throws Exception
7616
     * @throws HTML_QuickForm_Error
7617
     *
7618
     * @return string
7619
     */
7620
    public function display_edit_item($item_id, $excludeExtraFields = [])
7621
    {
7622
        $course_id = api_get_course_int_id();
7623
        $return = '';
7624
        $item_id = (int) $item_id;
7625
7626
        if (empty($item_id)) {
7627
            return '';
7628
        }
7629
7630
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7631
        $sql = "SELECT * FROM $tbl_lp_item
7632
                WHERE iid = ".$item_id;
7633
        $res = Database::query($sql);
7634
        $row = Database::fetch_array($res);
7635
        switch ($row['item_type']) {
7636
            case 'dir':
7637
            case 'asset':
7638
            case 'sco':
7639
            if (isset($_GET['view']) && $_GET['view'] == 'build') {
7640
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7641
                $return .= $this->display_item_form(
7642
                        $row['item_type'],
7643
                        get_lang('EditCurrentChapter').' :',
7644
                        'edit',
7645
                        $item_id,
7646
                        $row
7647
                    );
7648
            } else {
7649
                $return .= $this->display_item_form(
7650
                        $row['item_type'],
7651
                        get_lang('EditCurrentChapter').' :',
7652
                        'edit_item',
7653
                        $item_id,
7654
                        $row
7655
                    );
7656
            }
7657
                break;
7658
            case TOOL_DOCUMENT:
7659
            case TOOL_READOUT_TEXT:
7660
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7661
                $sql = "SELECT lp.*, doc.path as dir
7662
                        FROM $tbl_lp_item as lp
7663
                        LEFT JOIN $tbl_doc as doc
7664
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7665
                        WHERE
7666
                            doc.c_id = $course_id AND
7667
                            lp.iid = ".$item_id;
7668
                $res_step = Database::query($sql);
7669
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7670
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7671
7672
                if ($row['item_type'] === TOOL_DOCUMENT) {
7673
                    $return .= $this->display_document_form(
7674
                        'edit',
7675
                        $item_id,
7676
                        $row_step,
7677
                        null,
7678
                        $excludeExtraFields
7679
                    );
7680
                }
7681
7682
                if ($row['item_type'] === TOOL_READOUT_TEXT) {
7683
                    $return .= $this->displayFrmReadOutText('edit', $item_id, $row_step);
7684
                }
7685
                break;
7686
            case TOOL_LINK:
7687
                $linkId = (int) $row['path'];
7688
                if (!empty($linkId)) {
7689
                    $table = Database::get_course_table(TABLE_LINK);
7690
                    $sql = 'SELECT url FROM '.$table.'
7691
                            WHERE c_id = '.$course_id.' AND iid = '.$linkId;
7692
                    $res_link = Database::query($sql);
7693
                    $row_link = Database::fetch_array($res_link);
7694
                    if (empty($row_link)) {
7695
                        // Try with id
7696
                        $sql = 'SELECT url FROM '.$table.'
7697
                                WHERE c_id = '.$course_id.' AND id = '.$linkId;
7698
                        $res_link = Database::query($sql);
7699
                        $row_link = Database::fetch_array($res_link);
7700
                    }
7701
7702
                    if (is_array($row_link)) {
7703
                        $row['url'] = $row_link['url'];
7704
                    }
7705
                }
7706
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7707
                $return .= $this->display_link_form('edit', $item_id, $row, null, $excludeExtraFields);
7708
                break;
7709
            case TOOL_LP_FINAL_ITEM:
7710
                Session::write('finalItem', true);
7711
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7712
                $sql = "SELECT lp.*, doc.path as dir
7713
                        FROM $tbl_lp_item as lp
7714
                        LEFT JOIN $tbl_doc as doc
7715
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7716
                        WHERE
7717
                            doc.c_id = $course_id AND
7718
                            lp.iid = ".$item_id;
7719
                $res_step = Database::query($sql);
7720
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7721
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7722
                $return .= $this->display_document_form(
7723
                    'edit',
7724
                    $item_id,
7725
                    $row_step,
7726
                    null,
7727
                    $excludeExtraFields
7728
                );
7729
                break;
7730
            case TOOL_QUIZ:
7731
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7732
                $return .= $this->display_quiz_form('edit', $item_id, $row, $excludeExtraFields);
7733
                break;
7734
            case TOOL_HOTPOTATOES:
7735
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7736
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7737
                break;
7738
            case TOOL_STUDENTPUBLICATION:
7739
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7740
                $return .= $this->display_student_publication_form('edit', $item_id, $row, null, $excludeExtraFields);
7741
                break;
7742
            case TOOL_FORUM:
7743
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7744
                $return .= $this->display_forum_form('edit', $item_id, $row, $excludeExtraFields);
7745
                break;
7746
            case TOOL_THREAD:
7747
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7748
                $return .= $this->display_thread_form('edit', $item_id, $row);
7749
                break;
7750
            case TOOL_SURVEY:
7751
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7752
                $return .= $this->displaySurveyForm('edit', $item_id, $row);
7753
                break;
7754
        }
7755
7756
        return $return;
7757
    }
7758
7759
    /**
7760
     * Function that displays a list with al the resources that
7761
     * could be added to the learning path.
7762
     *
7763
     * @throws Exception
7764
     * @throws HTML_QuickForm_Error
7765
     *
7766
     * @return bool
7767
     */
7768
    public function display_resources()
7769
    {
7770
        $course_code = api_get_course_id();
7771
7772
        // Get all the docs.
7773
        $documents = $this->get_documents(true);
7774
7775
        // Get all the exercises.
7776
        $exercises = $this->get_exercises();
7777
7778
        // Get all the links.
7779
        $links = $this->get_links();
7780
7781
        // Get all the student publications.
7782
        $works = $this->get_student_publications();
7783
7784
        // Get all the forums.
7785
        $forums = $this->get_forums(null, $course_code);
7786
7787
        $dir = $this->display_item_form('dir', get_lang('EnterDataNewChapter'), 'add_item');
7788
7789
        // Get the final item form (see BT#11048) .
7790
        $finish = $this->getFinalItemForm();
7791
7792
        $headers = [
7793
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7794
            Display::return_icon('quiz.png', get_lang('Quiz'), [], ICON_SIZE_BIG),
7795
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7796
            Display::return_icon('works.png', get_lang('Works'), [], ICON_SIZE_BIG),
7797
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7798
            Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), [], ICON_SIZE_BIG),
7799
        ];
7800
7801
        $items = [
7802
            $documents,
7803
            $exercises,
7804
            $links,
7805
            $works,
7806
            $forums,
7807
            $dir,
7808
        ];
7809
7810
        $allowSurveyTool = api_get_configuration_value('allow_survey_tool_in_lp');
7811
        if ($allowSurveyTool) {
7812
            // Get all the surveys
7813
            $surveys = $this->getSurveys();
7814
            $items[] = $surveys;
7815
            $headers[] = Display::return_icon('survey.png', get_lang('Surveys'), [], ICON_SIZE_BIG);
7816
        }
7817
7818
        $xApiPlugin = XApiPlugin::create();
7819
        if ($xApiPlugin->isEnabled()) {
7820
            $headers[] = Display::return_icon(
7821
                'import_scorm.png',
7822
                get_lang($xApiPlugin->get_lang('ToolTinCan')),
7823
                [],
7824
                ICON_SIZE_BIG
7825
            );
7826
            $items[] = $xApiPlugin->getLpResourceBlock($this->lp_id);
7827
        }
7828
7829
        $h5pImportPlugin = H5pImportPlugin::create();
7830
        if ($h5pImportPlugin->isEnabled()) {
7831
            $headers[] = Display::return_icon(
7832
                'plugin_h5p_import_upload.png',
7833
                $h5pImportPlugin->get_lang('plugin_title'),
7834
                [],
7835
                ICON_SIZE_BIG
7836
            );
7837
            $items[] = $h5pImportPlugin->getLpResourceBlock($this->lp_id);
7838
        }
7839
7840
        $headers[] = Display::return_icon('flag_checkered.png', get_lang('Certificate'), [], ICON_SIZE_BIG);
7841
        $items[] = $finish;
7842
7843
        echo Display::return_message(get_lang('ClickOnTheLearnerViewToSeeYourLearningPath'), 'normal');
7844
7845
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
7846
7847
        echo Display::tabs(
7848
            $headers,
7849
            $items,
7850
            'resource_tab',
7851
            [],
7852
            [],
7853
            $selected
7854
        );
7855
7856
        return true;
7857
    }
7858
7859
    /**
7860
     * Returns the extension of a document.
7861
     *
7862
     * @param string $filename
7863
     *
7864
     * @return string Extension (part after the last dot)
7865
     */
7866
    public function get_extension($filename)
7867
    {
7868
        $explode = explode('.', $filename);
7869
7870
        return $explode[count($explode) - 1];
7871
    }
7872
7873
    /**
7874
     * Displays a document by id.
7875
     *
7876
     * @param int  $id
7877
     * @param bool $show_title
7878
     * @param bool $iframe
7879
     * @param bool $edit_link
7880
     *
7881
     * @return string
7882
     */
7883
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7884
    {
7885
        $_course = api_get_course_info();
7886
        $course_id = api_get_course_int_id();
7887
        $id = (int) $id;
7888
        $return = '';
7889
        $table = Database::get_course_table(TABLE_DOCUMENT);
7890
        $sql_doc = "SELECT * FROM $table
7891
                    WHERE c_id = $course_id AND iid = $id";
7892
        $res_doc = Database::query($sql_doc);
7893
        $row_doc = Database::fetch_array($res_doc);
7894
7895
        // TODO: Add a path filter.
7896
        if ($iframe) {
7897
            $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>';
7898
        } else {
7899
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7900
        }
7901
7902
        return $return;
7903
    }
7904
7905
    /**
7906
     * Return HTML form to add/edit a quiz.
7907
     *
7908
     * @param string $action     Action (add/edit)
7909
     * @param int    $id         Item ID if already exists
7910
     * @param mixed  $extra_info Extra information (quiz ID if integer)
7911
     *
7912
     * @throws Exception
7913
     *
7914
     * @return string HTML form
7915
     */
7916
    public function display_quiz_form(
7917
        $action = 'add',
7918
        $id = 0,
7919
        $extra_info = '',
7920
        $excludeExtraFields = []
7921
    ) {
7922
        $course_id = api_get_course_int_id();
7923
        $id = (int) $id;
7924
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7925
7926
        if ($id != 0 && is_array($extra_info)) {
7927
            $item_title = $extra_info['title'];
7928
            $item_description = $extra_info['description'];
7929
        } elseif (is_numeric($extra_info)) {
7930
            $sql = "SELECT title, description
7931
                    FROM $tbl_quiz
7932
                    WHERE iid = $extra_info";
7933
7934
            $result = Database::query($sql);
7935
            $row = Database::fetch_array($result);
7936
            $item_title = $row['title'];
7937
            $item_description = $row['description'];
7938
        } else {
7939
            $item_title = '';
7940
            $item_description = '';
7941
        }
7942
        $item_title = Security::remove_XSS($item_title);
7943
        $item_description = Security::remove_XSS($item_description);
7944
7945
        $parent = 0;
7946
        if ($id != 0 && is_array($extra_info)) {
7947
            $parent = $extra_info['parent_item_id'];
7948
        }
7949
7950
        $arrLP = $this->getItemsForForm();
7951
        $this->tree_array($arrLP);
7952
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7953
        unset($this->arrMenu);
7954
7955
        $form = new FormValidator(
7956
            'quiz_form',
7957
            'POST',
7958
            $this->getCurrentBuildingModeURL()
7959
        );
7960
        $defaults = [];
7961
7962
        if ($action === 'add') {
7963
            $legend = get_lang('CreateTheExercise');
7964
        } elseif ($action === 'move') {
7965
            $legend = get_lang('MoveTheCurrentExercise');
7966
        } else {
7967
            $legend = get_lang('EditCurrentExecice');
7968
        }
7969
7970
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7971
            $legend .= Display::return_message(get_lang('Warning').' ! '.get_lang('WarningEditingDocument'));
7972
        }
7973
7974
        $form->addHeader($legend);
7975
7976
        if ($action != 'move') {
7977
            $this->setItemTitle($form);
7978
            $defaults['title'] = $item_title;
7979
        }
7980
7981
        // Select for Parent item, root or chapter
7982
        $selectParent = $form->addSelect(
7983
            'parent',
7984
            get_lang('Parent'),
7985
            [],
7986
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7987
        );
7988
        $selectParent->addOption($this->name, 0);
7989
7990
        $arrHide = [
7991
            $id,
7992
        ];
7993
        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...
7994
            if ($action != 'add') {
7995
                if (
7996
                    ($arrLP[$i]['item_type'] == 'dir') &&
7997
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7998
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7999
                ) {
8000
                    $selectParent->addOption(
8001
                        $arrLP[$i]['title'],
8002
                        $arrLP[$i]['id'],
8003
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8004
                    );
8005
8006
                    if ($parent == $arrLP[$i]['id']) {
8007
                        $selectParent->setSelected($arrLP[$i]['id']);
8008
                    }
8009
                } else {
8010
                    $arrHide[] = $arrLP[$i]['id'];
8011
                }
8012
            } else {
8013
                if ($arrLP[$i]['item_type'] == 'dir') {
8014
                    $selectParent->addOption(
8015
                        $arrLP[$i]['title'],
8016
                        $arrLP[$i]['id'],
8017
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8018
                    );
8019
8020
                    if ($parent == $arrLP[$i]['id']) {
8021
                        $selectParent->setSelected($arrLP[$i]['id']);
8022
                    }
8023
                }
8024
            }
8025
        }
8026
8027
        if (is_array($arrLP)) {
8028
            reset($arrLP);
8029
        }
8030
8031
        $selectPrevious = $form->addSelect(
8032
            'previous',
8033
            get_lang('Position'),
8034
            [],
8035
            ['id' => 'previous']
8036
        );
8037
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8038
8039
        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...
8040
            if ($arrLP[$i]['parent_item_id'] == $parent &&
8041
                $arrLP[$i]['id'] != $id
8042
            ) {
8043
                $selectPrevious->addOption(
8044
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8045
                    $arrLP[$i]['id']
8046
                );
8047
8048
                if (is_array($extra_info)) {
8049
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8050
                        $selectPrevious->setSelected($arrLP[$i]['id']);
8051
                    }
8052
                } elseif ($action == 'add') {
8053
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8054
                }
8055
            }
8056
        }
8057
8058
        if ($action != 'move') {
8059
            $arrHide = [];
8060
            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...
8061
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8062
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8063
                }
8064
            }
8065
        }
8066
8067
        if ('edit' === $action) {
8068
            if (true !== api_get_configuration_value('lp_item_prerequisite_dates')) {
8069
                $excludeExtraFields = array_merge($excludeExtraFields, ['start_date', 'end_date']);
8070
            }
8071
            $extraField = new ExtraField('lp_item');
8072
            $extraField->addElements($form, $id, $excludeExtraFields);
8073
        }
8074
8075
        if ($action === 'add') {
8076
            $form->addButtonSave(get_lang('AddExercise'), 'submit_button');
8077
        } else {
8078
            $form->addButtonSave(get_lang('EditCurrentExecice'), 'submit_button');
8079
        }
8080
8081
        if ($action === 'move') {
8082
            $form->addHidden('title', $item_title);
8083
            $form->addHidden('description', $item_description);
8084
        }
8085
8086
        if (is_numeric($extra_info)) {
8087
            $form->addHidden('path', $extra_info);
8088
        } elseif (is_array($extra_info)) {
8089
            $form->addHidden('path', $extra_info['path']);
8090
        }
8091
8092
        $form->addHidden('type', TOOL_QUIZ);
8093
        $form->addHidden('post_time', time());
8094
        $this->setAuthorLpItem($form);
8095
        $form->setDefaults($defaults);
8096
8097
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
8098
    }
8099
8100
    /**
8101
     * Addition of Hotpotatoes tests.
8102
     *
8103
     * @param string $action
8104
     * @param int    $id         Internal ID of the item
8105
     * @param string $extra_info
8106
     *
8107
     * @return string HTML structure to display the hotpotatoes addition formular
8108
     */
8109
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
8110
    {
8111
        $course_id = api_get_course_int_id();
8112
        $uploadPath = DIR_HOTPOTATOES;
8113
8114
        if ($id != 0 && is_array($extra_info)) {
8115
            $item_title = stripslashes($extra_info['title']);
8116
            $item_description = stripslashes($extra_info['description']);
8117
        } elseif (is_numeric($extra_info)) {
8118
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
8119
8120
            $sql = "SELECT * FROM $TBL_DOCUMENT
8121
                    WHERE
8122
                        c_id = $course_id AND
8123
                        path LIKE '".$uploadPath."/%/%htm%' AND
8124
                        iid = ".(int) $extra_info."
8125
                    ORDER BY iid ASC";
8126
8127
            $res_hot = Database::query($sql);
8128
            $row = Database::fetch_array($res_hot);
8129
8130
            $item_title = $row['title'];
8131
            $item_description = $row['description'];
8132
8133
            if (!empty($row['comment'])) {
8134
                $item_title = $row['comment'];
8135
            }
8136
        } else {
8137
            $item_title = '';
8138
            $item_description = '';
8139
        }
8140
8141
        $parent = 0;
8142
        if ($id != 0 && is_array($extra_info)) {
8143
            $parent = $extra_info['parent_item_id'];
8144
        }
8145
8146
        $arrLP = $this->getItemsForForm();
8147
        $legend = '<legend>';
8148
        if ($action == 'add') {
8149
            $legend .= get_lang('CreateTheExercise');
8150
        } elseif ($action == 'move') {
8151
            $legend .= get_lang('MoveTheCurrentExercise');
8152
        } else {
8153
            $legend .= get_lang('EditCurrentExecice');
8154
        }
8155
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
8156
            $legend .= Display::return_message(
8157
                get_lang('Warning').' ! '.get_lang('WarningEditingDocument')
8158
            );
8159
        }
8160
        $legend .= '</legend>';
8161
8162
        $return = '<form method="POST">';
8163
        $return .= $legend;
8164
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
8165
        $return .= '<tr>';
8166
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
8167
        $return .= '<td class="input">';
8168
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
8169
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
8170
        $arrHide = [$id];
8171
8172
        if (count($arrLP) > 0) {
8173
            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...
8174
                if ($action != 'add') {
8175
                    if ($arrLP[$i]['item_type'] == 'dir' &&
8176
                        !in_array($arrLP[$i]['id'], $arrHide) &&
8177
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8178
                    ) {
8179
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
8180
                    } else {
8181
                        $arrHide[] = $arrLP[$i]['id'];
8182
                    }
8183
                } else {
8184
                    if ($arrLP[$i]['item_type'] == 'dir') {
8185
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
8186
                    }
8187
                }
8188
            }
8189
            reset($arrLP);
8190
        }
8191
8192
        $return .= '</select>';
8193
        $return .= '</td>';
8194
        $return .= '</tr>';
8195
        $return .= '<tr>';
8196
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
8197
        $return .= '<td class="input">';
8198
        $return .= '<select id="previous" name="previous" size="1">';
8199
        $return .= '<option class="top" value="0">'.get_lang('FirstPosition').'</option>';
8200
8201
        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...
8202
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8203
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8204
                    $selected = 'selected="selected" ';
8205
                } elseif ($action == 'add') {
8206
                    $selected = 'selected="selected" ';
8207
                } else {
8208
                    $selected = '';
8209
                }
8210
8211
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.
8212
                    get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
8213
            }
8214
        }
8215
8216
        $return .= '</select>';
8217
        $return .= '</td>';
8218
        $return .= '</tr>';
8219
8220
        if ($action != 'move') {
8221
            $return .= '<tr>';
8222
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
8223
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
8224
            $return .= '</tr>';
8225
            $id_prerequisite = 0;
8226
            if (is_array($arrLP) && count($arrLP) > 0) {
8227
                foreach ($arrLP as $key => $value) {
8228
                    if ($value['id'] == $id) {
8229
                        $id_prerequisite = $value['prerequisite'];
8230
                        break;
8231
                    }
8232
                }
8233
8234
                $arrHide = [];
8235
                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...
8236
                    if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8237
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8238
                    }
8239
                }
8240
            }
8241
        }
8242
8243
        $return .= '<tr>';
8244
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.
8245
            get_lang('SaveHotpotatoes').'</button></td>';
8246
        $return .= '</tr>';
8247
        $return .= '</table>';
8248
8249
        if ($action == 'move') {
8250
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
8251
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
8252
        }
8253
8254
        if (is_numeric($extra_info)) {
8255
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
8256
        } elseif (is_array($extra_info)) {
8257
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
8258
        }
8259
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
8260
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
8261
        $return .= '</form>';
8262
8263
        return $return;
8264
    }
8265
8266
    public function displaySurveyForm(
8267
      $action = 'add',
8268
      $id = 0,
8269
      $extraInfo = '',
8270
      $excludeExtraFields = []
8271
    ) {
8272
        $courseId = api_get_course_int_id();
8273
        $tblSurvey = Database::get_course_table(TABLE_SURVEY);
8274
8275
        $itemTitle = '';
8276
        $itemDescription = '';
8277
8278
        if ($id != 0 && is_array($extraInfo)) {
8279
            $itemTitle = stripslashes($extraInfo['title']);
8280
        } elseif (is_numeric($extraInfo)) {
8281
            $sql = "SELECT title, intro as comment
8282
                    FROM $tblSurvey
8283
                    WHERE c_id = $courseId AND survey_id = ".(int) $extraInfo;
8284
8285
            $result = Database::query($sql);
8286
            $row = Database::fetch_array($result);
8287
8288
            $itemTitle = strip_tags($row['title']);
8289
            $itemDescription = $row['comment'];
8290
        }
8291
        $parent = 0;
8292
        if ($id != 0 && is_array($extraInfo)) {
8293
            $parent = $extraInfo['parent_item_id'];
8294
        }
8295
        $arrLP = $this->getItemsForForm();
8296
        $this->tree_array($arrLP);
8297
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8298
        unset($this->arrMenu);
8299
8300
        if ($action == 'add') {
8301
            $legend = get_lang('CreateSurvey');
8302
        } elseif ($action == 'move') {
8303
            $legend = get_lang('MoveTheCurrentSurvey');
8304
        } else {
8305
            $legend = get_lang('ModifySurveyInformation');
8306
        }
8307
8308
        $form = new FormValidator(
8309
            'survey_form',
8310
            'POST',
8311
            $this->getCurrentBuildingModeURL()
8312
        );
8313
        $defaults = [];
8314
8315
        $form->addHeader($legend);
8316
8317
        if ($action != 'move') {
8318
            $this->setItemTitle($form);
8319
            $defaults['title'] = $itemTitle;
8320
        }
8321
8322
        $selectParent = $form->addSelect(
8323
            'parent',
8324
            get_lang('Parent'),
8325
            [],
8326
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
8327
        );
8328
        $selectParent->addOption($this->name, 0);
8329
        $arrHide = [
8330
            $id,
8331
        ];
8332
        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...
8333
            if ($action != 'add') {
8334
                if ($arrLP[$i]['item_type'] == 'dir' &&
8335
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8336
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8337
                ) {
8338
                    $selectParent->addOption(
8339
                        $arrLP[$i]['title'],
8340
                        $arrLP[$i]['id'],
8341
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8342
                    );
8343
8344
                    if ($parent == $arrLP[$i]['id']) {
8345
                        $selectParent->setSelected($arrLP[$i]['id']);
8346
                    }
8347
                } else {
8348
                    $arrHide[] = $arrLP[$i]['id'];
8349
                }
8350
            } else {
8351
                if ($arrLP[$i]['item_type'] == 'dir') {
8352
                    $selectParent->addOption(
8353
                        $arrLP[$i]['title'],
8354
                        $arrLP[$i]['id'],
8355
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8356
                    );
8357
8358
                    if ($parent == $arrLP[$i]['id']) {
8359
                        $selectParent->setSelected($arrLP[$i]['id']);
8360
                    }
8361
                }
8362
            }
8363
        }
8364
8365
        if (is_array($arrLP)) {
8366
            reset($arrLP);
8367
        }
8368
8369
        $selectPrevious = $form->addSelect(
8370
            'previous',
8371
            get_lang('Position'),
8372
            [],
8373
            ['id' => 'previous', 'class' => 'learnpath_item_form']
8374
        );
8375
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8376
8377
        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...
8378
            if ($arrLP[$i]['parent_item_id'] == $parent &&
8379
                $arrLP[$i]['id'] != $id
8380
            ) {
8381
                $selectPrevious->addOption(
8382
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8383
                    $arrLP[$i]['id']
8384
                );
8385
8386
                if (isset($extra_info['previous_item_id']) &&
8387
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8388
                ) {
8389
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8390
                } elseif ($action == 'add') {
8391
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8392
                }
8393
            }
8394
        }
8395
8396
        if ($action != 'move') {
8397
            $idPrerequisite = 0;
8398
            if (is_array($arrLP)) {
8399
                foreach ($arrLP as $key => $value) {
8400
                    if ($value['id'] == $id) {
8401
                        $idPrerequisite = $value['prerequisite'];
8402
                        break;
8403
                    }
8404
                }
8405
            }
8406
8407
            $arrHide = [];
8408
            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...
8409
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8410
                    if (isset($extra_info['previous_item_id']) &&
8411
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
8412
                    ) {
8413
                        $sSelectedPosition = $arrLP[$i]['id'];
8414
                    } elseif ($action == 'add') {
8415
                        $sSelectedPosition = 0;
8416
                    }
8417
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8418
                }
8419
            }
8420
        }
8421
8422
        if ('edit' === $action) {
8423
            if (true !== api_get_configuration_value('lp_item_prerequisite_dates')) {
8424
                $excludeExtraFields = array_merge($excludeExtraFields, ['start_date', 'end_date']);
8425
            }
8426
            $extraField = new ExtraField('lp_item');
8427
            $extraField->addElements($form, $id, $excludeExtraFields);
8428
        }
8429
8430
        if ($action == 'add') {
8431
            $form->addButtonSave(get_lang('AddSurveyToCourse'), 'submit_button');
8432
        } else {
8433
            $form->addButtonSave(get_lang('EditCurrentSurvey'), 'submit_button');
8434
        }
8435
8436
        if ($action == 'move') {
8437
            $form->addHidden('title', $itemTitle);
8438
            $form->addHidden('description', $itemDescription);
8439
        }
8440
8441
        if (is_numeric($extraInfo)) {
8442
            $form->addHidden('path', $extraInfo);
8443
        } elseif (is_array($extraInfo)) {
8444
            $form->addHidden('path', $extraInfo['path']);
8445
        }
8446
        $form->addHidden('type', TOOL_SURVEY);
8447
        $form->addHidden('post_time', time());
8448
        $this->setAuthorLpItem($form);
8449
        $form->setDefaults($defaults);
8450
8451
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
8452
    }
8453
8454
    /**
8455
     * Return the form to display the forum edit/add option.
8456
     *
8457
     * @param string $action
8458
     * @param int    $id         ID of the lp_item if already exists
8459
     * @param string $extra_info
8460
     *
8461
     * @throws Exception
8462
     *
8463
     * @return string HTML form
8464
     */
8465
    public function display_forum_form(
8466
        $action = 'add',
8467
        $id = 0,
8468
        $extra_info = '',
8469
        $excludeExtraFields = []
8470
    ) {
8471
        $course_id = api_get_course_int_id();
8472
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
8473
8474
        $item_title = '';
8475
        $item_description = '';
8476
8477
        if ($id != 0 && is_array($extra_info)) {
8478
            $item_title = stripslashes($extra_info['title']);
8479
        } elseif (is_numeric($extra_info)) {
8480
            $sql = "SELECT forum_title as title, forum_comment as comment
8481
                    FROM $tbl_forum
8482
                    WHERE c_id = $course_id AND forum_id = ".$extra_info;
8483
8484
            $result = Database::query($sql);
8485
            $row = Database::fetch_array($result);
8486
8487
            $item_title = $row['title'];
8488
            $item_description = $row['comment'];
8489
        }
8490
        $parent = 0;
8491
        if ($id != 0 && is_array($extra_info)) {
8492
            $parent = $extra_info['parent_item_id'];
8493
        }
8494
        $arrLP = $this->getItemsForForm();
8495
        $this->tree_array($arrLP);
8496
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8497
        unset($this->arrMenu);
8498
8499
        if ($action == 'add') {
8500
            $legend = get_lang('CreateTheForum');
8501
        } elseif ($action == 'move') {
8502
            $legend = get_lang('MoveTheCurrentForum');
8503
        } else {
8504
            $legend = get_lang('EditCurrentForum');
8505
        }
8506
8507
        $form = new FormValidator(
8508
            'forum_form',
8509
            'POST',
8510
            $this->getCurrentBuildingModeURL()
8511
        );
8512
        $defaults = [];
8513
8514
        $form->addHeader($legend);
8515
8516
        if ($action != 'move') {
8517
            $this->setItemTitle($form);
8518
            $defaults['title'] = $item_title;
8519
        }
8520
8521
        $selectParent = $form->addSelect(
8522
            'parent',
8523
            get_lang('Parent'),
8524
            [],
8525
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
8526
        );
8527
        $selectParent->addOption($this->name, 0);
8528
        $arrHide = [
8529
            $id,
8530
        ];
8531
        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...
8532
            if ($action != 'add') {
8533
                if ($arrLP[$i]['item_type'] == 'dir' &&
8534
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8535
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8536
                ) {
8537
                    $selectParent->addOption(
8538
                        $arrLP[$i]['title'],
8539
                        $arrLP[$i]['id'],
8540
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8541
                    );
8542
8543
                    if ($parent == $arrLP[$i]['id']) {
8544
                        $selectParent->setSelected($arrLP[$i]['id']);
8545
                    }
8546
                } else {
8547
                    $arrHide[] = $arrLP[$i]['id'];
8548
                }
8549
            } else {
8550
                if ($arrLP[$i]['item_type'] == 'dir') {
8551
                    $selectParent->addOption(
8552
                        $arrLP[$i]['title'],
8553
                        $arrLP[$i]['id'],
8554
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8555
                    );
8556
8557
                    if ($parent == $arrLP[$i]['id']) {
8558
                        $selectParent->setSelected($arrLP[$i]['id']);
8559
                    }
8560
                }
8561
            }
8562
        }
8563
8564
        if (is_array($arrLP)) {
8565
            reset($arrLP);
8566
        }
8567
8568
        $selectPrevious = $form->addSelect(
8569
            'previous',
8570
            get_lang('Position'),
8571
            [],
8572
            ['id' => 'previous', 'class' => 'learnpath_item_form']
8573
        );
8574
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8575
8576
        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...
8577
            if ($arrLP[$i]['parent_item_id'] == $parent &&
8578
                $arrLP[$i]['id'] != $id
8579
            ) {
8580
                $selectPrevious->addOption(
8581
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8582
                    $arrLP[$i]['id']
8583
                );
8584
8585
                if (isset($extra_info['previous_item_id']) &&
8586
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8587
                ) {
8588
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8589
                } elseif ($action == 'add') {
8590
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8591
                }
8592
            }
8593
        }
8594
8595
        if ($action != 'move') {
8596
            $id_prerequisite = 0;
8597
            if (is_array($arrLP)) {
8598
                foreach ($arrLP as $key => $value) {
8599
                    if ($value['id'] == $id) {
8600
                        $id_prerequisite = $value['prerequisite'];
8601
                        break;
8602
                    }
8603
                }
8604
            }
8605
8606
            $arrHide = [];
8607
            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...
8608
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8609
                    if (isset($extra_info['previous_item_id']) &&
8610
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
8611
                    ) {
8612
                        $s_selected_position = $arrLP[$i]['id'];
8613
                    } elseif ($action == 'add') {
8614
                        $s_selected_position = 0;
8615
                    }
8616
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8617
                }
8618
            }
8619
        }
8620
8621
        if ('edit' === $action) {
8622
            if (true !== api_get_configuration_value('lp_item_prerequisite_dates')) {
8623
                $excludeExtraFields = array_merge($excludeExtraFields, ['start_date', 'end_date']);
8624
            }
8625
            $extraField = new ExtraField('lp_item');
8626
            $extraField->addElements($form, $id, $excludeExtraFields);
8627
        }
8628
8629
        if ($action == 'add') {
8630
            $form->addButtonSave(get_lang('AddForumToCourse'), 'submit_button');
8631
        } else {
8632
            $form->addButtonSave(get_lang('EditCurrentForum'), 'submit_button');
8633
        }
8634
8635
        if ($action == 'move') {
8636
            $form->addHidden('title', $item_title);
8637
            $form->addHidden('description', $item_description);
8638
        }
8639
8640
        if (is_numeric($extra_info)) {
8641
            $form->addHidden('path', $extra_info);
8642
        } elseif (is_array($extra_info)) {
8643
            $form->addHidden('path', $extra_info['path']);
8644
        }
8645
        $form->addHidden('type', TOOL_FORUM);
8646
        $form->addHidden('post_time', time());
8647
        $this->setAuthorLpItem($form);
8648
        $form->setDefaults($defaults);
8649
8650
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
8651
    }
8652
8653
    /**
8654
     * Return HTML form to add/edit forum threads.
8655
     *
8656
     * @param string $action
8657
     * @param int    $id         Item ID if already exists in learning path
8658
     * @param string $extra_info
8659
     *
8660
     * @throws Exception
8661
     *
8662
     * @return string HTML form
8663
     */
8664
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
8665
    {
8666
        $course_id = api_get_course_int_id();
8667
        if (empty($course_id)) {
8668
            return null;
8669
        }
8670
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
8671
8672
        $item_title = '';
8673
        $item_description = '';
8674
        if ($id != 0 && is_array($extra_info)) {
8675
            $item_title = stripslashes($extra_info['title']);
8676
        } elseif (is_numeric($extra_info)) {
8677
            $sql = "SELECT thread_title as title FROM $tbl_forum
8678
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
8679
8680
            $result = Database::query($sql);
8681
            $row = Database::fetch_array($result);
8682
8683
            $item_title = $row['title'];
8684
            $item_description = '';
8685
        }
8686
8687
        $parent = 0;
8688
        if ($id != 0 && is_array($extra_info)) {
8689
            $parent = $extra_info['parent_item_id'];
8690
        }
8691
8692
        $arrLP = $this->getItemsForForm();
8693
        $this->tree_array($arrLP);
8694
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8695
        unset($this->arrMenu);
8696
8697
        $form = new FormValidator(
8698
            'thread_form',
8699
            'POST',
8700
            $this->getCurrentBuildingModeURL()
8701
        );
8702
        $defaults = [];
8703
8704
        if ($action == 'add') {
8705
            $legend = get_lang('CreateTheForum');
8706
        } elseif ($action == 'move') {
8707
            $legend = get_lang('MoveTheCurrentForum');
8708
        } else {
8709
            $legend = get_lang('EditCurrentForum');
8710
        }
8711
8712
        $form->addHeader($legend);
8713
        $selectParent = $form->addSelect(
8714
            'parent',
8715
            get_lang('Parent'),
8716
            [],
8717
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
8718
        );
8719
        $selectParent->addOption($this->name, 0);
8720
8721
        $arrHide = [
8722
            $id,
8723
        ];
8724
8725
        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...
8726
            if ($action != 'add') {
8727
                if (
8728
                    ($arrLP[$i]['item_type'] == 'dir') &&
8729
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8730
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8731
                ) {
8732
                    $selectParent->addOption(
8733
                        $arrLP[$i]['title'],
8734
                        $arrLP[$i]['id'],
8735
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8736
                    );
8737
8738
                    if ($parent == $arrLP[$i]['id']) {
8739
                        $selectParent->setSelected($arrLP[$i]['id']);
8740
                    }
8741
                } else {
8742
                    $arrHide[] = $arrLP[$i]['id'];
8743
                }
8744
            } else {
8745
                if ($arrLP[$i]['item_type'] == 'dir') {
8746
                    $selectParent->addOption(
8747
                        $arrLP[$i]['title'],
8748
                        $arrLP[$i]['id'],
8749
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8750
                    );
8751
8752
                    if ($parent == $arrLP[$i]['id']) {
8753
                        $selectParent->setSelected($arrLP[$i]['id']);
8754
                    }
8755
                }
8756
            }
8757
        }
8758
8759
        if ($arrLP != null) {
8760
            reset($arrLP);
8761
        }
8762
8763
        $selectPrevious = $form->addSelect(
8764
            'previous',
8765
            get_lang('Position'),
8766
            [],
8767
            ['id' => 'previous']
8768
        );
8769
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8770
8771
        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...
8772
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8773
                $selectPrevious->addOption(
8774
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8775
                    $arrLP[$i]['id']
8776
                );
8777
8778
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8779
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8780
                } elseif ($action == 'add') {
8781
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8782
                }
8783
            }
8784
        }
8785
8786
        if ($action != 'move') {
8787
            $this->setItemTitle($form);
8788
            $defaults['title'] = $item_title;
8789
8790
            $id_prerequisite = 0;
8791
            if ($arrLP != null) {
8792
                foreach ($arrLP as $key => $value) {
8793
                    if ($value['id'] == $id) {
8794
                        $id_prerequisite = $value['prerequisite'];
8795
                        break;
8796
                    }
8797
                }
8798
            }
8799
8800
            $arrHide = [];
8801
            $s_selected_position = 0;
8802
            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...
8803
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8804
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8805
                        $s_selected_position = $arrLP[$i]['id'];
8806
                    } elseif ($action == 'add') {
8807
                        $s_selected_position = 0;
8808
                    }
8809
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8810
                }
8811
            }
8812
8813
            $selectPrerequisites = $form->addSelect(
8814
                'prerequisites',
8815
                get_lang('LearnpathPrerequisites'),
8816
                [],
8817
                ['id' => 'prerequisites']
8818
            );
8819
            $selectPrerequisites->addOption(get_lang('NoPrerequisites'), 0);
8820
8821
            foreach ($arrHide as $key => $value) {
8822
                $selectPrerequisites->addOption($value['value'], $key);
8823
8824
                if ($key == $s_selected_position && $action == 'add') {
8825
                    $selectPrerequisites->setSelected($key);
8826
                } elseif ($key == $id_prerequisite && $action == 'edit') {
8827
                    $selectPrerequisites->setSelected($key);
8828
                }
8829
            }
8830
        }
8831
8832
        if ('edit' === $action) {
8833
            $excludeExtraFields = [];
8834
            if (true !== api_get_configuration_value('lp_item_prerequisite_dates')) {
8835
                $excludeExtraFields = ['start_date', 'end_date'];
8836
            }
8837
            $extraField = new ExtraField('lp_item');
8838
            $extraField->addElements($form, $id, $excludeExtraFields);
8839
        }
8840
8841
        $form->addButtonSave(get_lang('Ok'), 'submit_button');
8842
8843
        if ($action == 'move') {
8844
            $form->addHidden('title', $item_title);
8845
            $form->addHidden('description', $item_description);
8846
        }
8847
8848
        if (is_numeric($extra_info)) {
8849
            $form->addHidden('path', $extra_info);
8850
        } elseif (is_array($extra_info)) {
8851
            $form->addHidden('path', $extra_info['path']);
8852
        }
8853
8854
        $form->addHidden('type', TOOL_THREAD);
8855
        $form->addHidden('post_time', time());
8856
        $this->setAuthorLpItem($form);
8857
        $form->setDefaults($defaults);
8858
8859
        return $form->returnForm();
8860
    }
8861
8862
    /**
8863
     * Return the HTML form to display an item (generally a dir item).
8864
     *
8865
     * @param string $item_type
8866
     * @param string $title
8867
     * @param string $action
8868
     * @param int    $id
8869
     * @param string $extra_info
8870
     *
8871
     * @throws Exception
8872
     * @throws HTML_QuickForm_Error
8873
     *
8874
     * @return string HTML form
8875
     */
8876
    public function display_item_form(
8877
        $item_type,
8878
        $title = '',
8879
        $action = 'add_item',
8880
        $id = 0,
8881
        $extra_info = 'new'
8882
    ) {
8883
        $_course = api_get_course_info();
8884
8885
        global $charset;
8886
8887
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8888
        $item_title = '';
8889
        $item_description = '';
8890
        $item_path_fck = '';
8891
8892
        $parent = 0;
8893
        $previousId = null;
8894
        if ($id != 0 && is_array($extra_info)) {
8895
            $item_title = $extra_info['title'];
8896
            $item_description = $extra_info['description'];
8897
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8898
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8899
            $parent = $extra_info['parent_item_id'];
8900
            $previousId = $extra_info['previous_item_id'];
8901
        }
8902
8903
        if ($extra_info instanceof learnpathItem) {
8904
            $item_title = $extra_info->get_title();
8905
            $item_description = $extra_info->get_description();
8906
            $path = $extra_info->get_path();
8907
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($path);
8908
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($path);
8909
            $parent = $extra_info->get_parent();
8910
            $previousId = $extra_info->previous;
8911
        }
8912
8913
        $id = (int) $id;
8914
        $sql = "SELECT * FROM $tbl_lp_item
8915
                WHERE
8916
                    lp_id = ".$this->lp_id." AND
8917
                    iid != $id";
8918
8919
        if ($item_type === 'dir') {
8920
            $sql .= " AND parent_item_id = 0";
8921
        }
8922
8923
        $result = Database::query($sql);
8924
        $arrLP = [];
8925
        while ($row = Database::fetch_array($result)) {
8926
            $arrLP[] = [
8927
                'id' => $row['iid'],
8928
                'item_type' => $row['item_type'],
8929
                'title' => $this->cleanItemTitle($row['title']),
8930
                'title_raw' => $row['title'],
8931
                'path' => $row['path'],
8932
                'description' => $row['description'],
8933
                'parent_item_id' => $row['parent_item_id'],
8934
                'previous_item_id' => $row['previous_item_id'],
8935
                'next_item_id' => $row['next_item_id'],
8936
                'max_score' => $row['max_score'],
8937
                'min_score' => $row['min_score'],
8938
                'mastery_score' => $row['mastery_score'],
8939
                'prerequisite' => $row['prerequisite'],
8940
                'display_order' => $row['display_order'],
8941
            ];
8942
        }
8943
8944
        $this->tree_array($arrLP);
8945
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8946
        unset($this->arrMenu);
8947
8948
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8949
8950
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
8951
        $defaults['title'] = api_html_entity_decode(
8952
            $item_title,
8953
            ENT_QUOTES,
8954
            $charset
8955
        );
8956
        $defaults['description'] = $item_description;
8957
8958
        $form->addHeader($title);
8959
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
8960
        $arrHide[0]['padding'] = 20;
8961
        $charset = api_get_system_encoding();
8962
        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...
8963
            if ($action != 'add') {
8964
                if ($arrLP[$i]['item_type'] === 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8965
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8966
                ) {
8967
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8968
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8969
                    if ($parent == $arrLP[$i]['id']) {
8970
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8971
                    }
8972
                }
8973
            } else {
8974
                if ($arrLP[$i]['item_type'] === 'dir') {
8975
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8976
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8977
                    if ($parent == $arrLP[$i]['id']) {
8978
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8979
                    }
8980
                }
8981
            }
8982
        }
8983
8984
        if ($action !== 'move') {
8985
            $this->setItemTitle($form);
8986
        } else {
8987
            $form->addElement('hidden', 'title');
8988
        }
8989
8990
        $parentSelect = $form->addElement(
8991
            'select',
8992
            'parent',
8993
            get_lang('Parent'),
8994
            '',
8995
            [
8996
                'id' => 'idParent',
8997
                'onchange' => 'javascript: load_cbo(this.value);',
8998
            ]
8999
        );
9000
9001
        foreach ($arrHide as $key => $value) {
9002
            $parentSelect->addOption(
9003
                $value['value'],
9004
                $key,
9005
                'style="padding-left:'.$value['padding'].'px;"'
9006
            );
9007
            $lastPosition = $key;
9008
        }
9009
9010
        if (!empty($s_selected_parent)) {
9011
            $parentSelect->setSelected($s_selected_parent);
9012
        }
9013
9014
        if (is_array($arrLP)) {
9015
            reset($arrLP);
9016
        }
9017
9018
        $arrHide = [];
9019
        // POSITION
9020
        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...
9021
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id &&
9022
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
9023
                //this is the same!
9024
                if (isset($previousId) && $previousId == $arrLP[$i]['id']) {
9025
                    $s_selected_position = $arrLP[$i]['id'];
9026
                } elseif ($action === 'add') {
9027
                    $s_selected_position = $arrLP[$i]['id'];
9028
                }
9029
9030
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
9031
            }
9032
        }
9033
9034
        $position = $form->addElement(
9035
            'select',
9036
            'previous',
9037
            get_lang('Position'),
9038
            '',
9039
            ['id' => 'previous']
9040
        );
9041
        $padding = isset($value['padding']) ? $value['padding'] : 0;
9042
        $position->addOption(get_lang('FirstPosition'), 0, 'style="padding-left:'.$padding.'px;"');
9043
9044
        $lastPosition = null;
9045
        foreach ($arrHide as $key => $value) {
9046
            $position->addOption($value['value'], $key, 'style="padding-left:'.$padding.'px;"');
9047
            $lastPosition = $key;
9048
        }
9049
9050
        if (!empty($s_selected_position)) {
9051
            $position->setSelected($s_selected_position);
9052
        }
9053
9054
        // When new chapter add at the end
9055
        if ($action === 'add_item') {
9056
            $position->setSelected($lastPosition);
9057
        }
9058
9059
        if (is_array($arrLP)) {
9060
            reset($arrLP);
9061
        }
9062
9063
        $form->addButtonSave(get_lang('SaveSection'), 'submit_button');
9064
9065
        //fix in order to use the tab
9066
        if ($item_type === 'dir') {
9067
            $form->addElement('hidden', 'type', 'dir');
9068
        }
9069
9070
        $extension = null;
9071
        if (!empty($item_path)) {
9072
            $extension = pathinfo($item_path, PATHINFO_EXTENSION);
9073
        }
9074
9075
        //assets can't be modified
9076
        //$item_type == 'asset' ||
9077
        if (($item_type === 'sco') && ($extension === 'html' || $extension === 'htm')) {
9078
            if ($item_type === 'sco') {
9079
                $form->addElement(
9080
                    'html',
9081
                    '<script>alert("'.get_lang('WarningWhenEditingScorm').'")</script>'
9082
                );
9083
            }
9084
            $renderer = $form->defaultRenderer();
9085
            $renderer->setElementTemplate(
9086
                '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
9087
                'content_lp'
9088
            );
9089
9090
            $relative_prefix = '';
9091
            $editor_config = [
9092
                'ToolbarSet' => 'LearningPathDocuments',
9093
                'Width' => '100%',
9094
                'Height' => '500',
9095
                'FullPage' => true,
9096
                'CreateDocumentDir' => $relative_prefix,
9097
                'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
9098
                'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
9099
            ];
9100
9101
            $form->addHtmlEditor('content_lp', '', true, true, $editor_config);
9102
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
9103
            $defaults['content_lp'] = file_get_contents($content_path);
9104
        }
9105
9106
        if (!empty($id)) {
9107
            $form->addHidden('id', $id);
9108
        }
9109
9110
        $form->addElement('hidden', 'type', $item_type);
9111
        $form->addElement('hidden', 'post_time', time());
9112
        $form->setDefaults($defaults);
9113
9114
        return $form->returnForm();
9115
    }
9116
9117
    /**
9118
     * @return string
9119
     */
9120
    public function getCurrentBuildingModeURL()
9121
    {
9122
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
9123
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
9124
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
9125
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
9126
9127
        $currentUrl = api_get_self().'?'.api_get_cidreq().
9128
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
9129
9130
        return $currentUrl;
9131
    }
9132
9133
    /**
9134
     * Returns the form to update or create a document.
9135
     *
9136
     * @param string        $action     (add/edit)
9137
     * @param int           $id         ID of the lp_item (if already exists)
9138
     * @param mixed         $extra_info Integer if document ID, string if info ('new')
9139
     * @param learnpathItem $item
9140
     *
9141
     * @return string HTML form
9142
     */
9143
    public function display_document_form(
9144
        $action = 'add',
9145
        $id = 0,
9146
        $extra_info = 'new',
9147
        $item = null,
9148
        $excludeExtraFields = []
9149
    ) {
9150
        $course_id = api_get_course_int_id();
9151
        $_course = api_get_course_info();
9152
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
9153
9154
        $no_display_edit_textarea = false;
9155
        //If action==edit document
9156
        //We don't display the document form if it's not an editable document (html or txt file)
9157
        if ($action === 'edit') {
9158
            if (is_array($extra_info)) {
9159
                $path_parts = pathinfo($extra_info['dir']);
9160
                if ($path_parts['extension'] !== 'txt' && $path_parts['extension'] !== 'html') {
9161
                    $no_display_edit_textarea = true;
9162
                }
9163
            }
9164
        }
9165
        $no_display_add = false;
9166
        // If action==add an existing document
9167
        // We don't display the document form if it's not an editable document (html or txt file).
9168
        if ($action === 'add') {
9169
            if (is_numeric($extra_info)) {
9170
                $extra_info = (int) $extra_info;
9171
                $sql_doc = "SELECT path FROM $tbl_doc
9172
                            WHERE c_id = $course_id AND iid = ".$extra_info;
9173
                $result = Database::query($sql_doc);
9174
                $path_file = Database::result($result, 0, 0);
9175
                $path_parts = pathinfo($path_file);
9176
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
9177
                    $no_display_add = true;
9178
                }
9179
            }
9180
        }
9181
9182
        $item_title = '';
9183
        $item_description = '';
9184
        if ($id != 0 && is_array($extra_info)) {
9185
            $item_title = stripslashes($extra_info['title']);
9186
            $item_description = stripslashes($extra_info['description']);
9187
            if (empty($item_title)) {
9188
                $path_parts = pathinfo($extra_info['path']);
9189
                $item_title = stripslashes($path_parts['filename']);
9190
            }
9191
        } elseif (is_numeric($extra_info)) {
9192
            $sql = "SELECT path, title FROM $tbl_doc
9193
                    WHERE
9194
                        c_id = ".$course_id." AND
9195
                        iid = ".intval($extra_info);
9196
            $result = Database::query($sql);
9197
            $row = Database::fetch_array($result);
9198
            $item_title = $row['title'];
9199
            $item_title = str_replace('_', ' ', $item_title);
9200
            if (empty($item_title)) {
9201
                $path_parts = pathinfo($row['path']);
9202
                $item_title = stripslashes($path_parts['filename']);
9203
            }
9204
        }
9205
9206
        $return = '<legend>';
9207
        $parent = 0;
9208
        if ($id != 0 && is_array($extra_info)) {
9209
            $parent = $extra_info['parent_item_id'];
9210
        }
9211
9212
        $selectedPosition = 0;
9213
        if (is_array($extra_info) && isset($extra_info['previous_item_id'])) {
9214
            $selectedPosition = $extra_info['previous_item_id'];
9215
        }
9216
9217
        if ($item instanceof learnpathItem) {
9218
            $item_title = stripslashes($item->get_title());
9219
            $item_description = stripslashes($item->get_description());
9220
            $selectedPosition = $item->previous;
9221
            $parent = $item->parent;
9222
        }
9223
9224
        $arrLP = $this->getItemsForForm();
9225
        $this->tree_array($arrLP);
9226
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9227
        unset($this->arrMenu);
9228
9229
        if ($action === 'add') {
9230
            $return .= get_lang('CreateTheDocument');
9231
        } elseif ($action === 'move') {
9232
            $return .= get_lang('MoveTheCurrentDocument');
9233
        } else {
9234
            $return .= get_lang('EditTheCurrentDocument');
9235
        }
9236
        $return .= '</legend>';
9237
9238
        if (isset($_GET['edit']) && $_GET['edit'] === 'true') {
9239
            $return .= Display::return_message(
9240
                '<strong>'.get_lang('Warning').' !</strong><br />'.get_lang('WarningEditingDocument'),
9241
                false
9242
            );
9243
        }
9244
        $form = new FormValidator(
9245
            'form',
9246
            'POST',
9247
            $this->getCurrentBuildingModeURL(),
9248
            '',
9249
            ['enctype' => 'multipart/form-data']
9250
        );
9251
        $defaults['title'] = Security::remove_XSS($item_title);
9252
        if (empty($item_title)) {
9253
            $defaults['title'] = Security::remove_XSS($item_title);
9254
        }
9255
        $defaults['description'] = $item_description;
9256
        $form->addElement('html', $return);
9257
9258
        if ($action !== 'move') {
9259
            $data = $this->generate_lp_folder($_course);
9260
            if ($action !== 'edit') {
9261
                $folders = DocumentManager::get_all_document_folders(
9262
                    $_course,
9263
                    0,
9264
                    true
9265
                );
9266
                DocumentManager::build_directory_selector(
9267
                    $folders,
9268
                    '',
9269
                    [],
9270
                    true,
9271
                    $form,
9272
                    'directory_parent_id'
9273
                );
9274
            }
9275
9276
            if (isset($data['id'])) {
9277
                $defaults['directory_parent_id'] = $data['id'];
9278
            }
9279
            $this->setItemTitle($form);
9280
        }
9281
9282
        $arrHide[0]['value'] = $this->name;
9283
        $arrHide[0]['padding'] = 20;
9284
9285
        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...
9286
            if ($action !== 'add') {
9287
                if ($arrLP[$i]['item_type'] === 'dir' &&
9288
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9289
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9290
                ) {
9291
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9292
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9293
                }
9294
            } else {
9295
                if ($arrLP[$i]['item_type'] == 'dir') {
9296
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9297
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9298
                }
9299
            }
9300
        }
9301
9302
        $parentSelect = $form->addSelect(
9303
            'parent',
9304
            get_lang('Parent'),
9305
            [],
9306
            [
9307
                'id' => 'idParent',
9308
                'onchange' => 'javascript: load_cbo(this.value);',
9309
            ]
9310
        );
9311
9312
        $my_count = 0;
9313
        foreach ($arrHide as $key => $value) {
9314
            if ($my_count != 0) {
9315
                // The LP name is also the first section and is not in the same charset like the other sections.
9316
                $value['value'] = Security::remove_XSS($value['value']);
9317
                $parentSelect->addOption(
9318
                    $value['value'],
9319
                    $key,
9320
                    'style="padding-left:'.$value['padding'].'px;"'
9321
                );
9322
            } else {
9323
                $value['value'] = Security::remove_XSS($value['value']);
9324
                $parentSelect->addOption(
9325
                    $value['value'],
9326
                    $key,
9327
                    'style="padding-left:'.$value['padding'].'px;"'
9328
                );
9329
            }
9330
            $my_count++;
9331
        }
9332
9333
        if (!empty($id)) {
9334
            $parentSelect->setSelected($parent);
9335
        } else {
9336
            $parent_item_id = Session::read('parent_item_id', 0);
9337
            $parentSelect->setSelected($parent_item_id);
9338
        }
9339
9340
        if (is_array($arrLP)) {
9341
            reset($arrLP);
9342
        }
9343
9344
        $arrHide = [];
9345
        // POSITION
9346
        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...
9347
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
9348
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
9349
            ) {
9350
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
9351
            }
9352
        }
9353
9354
        $position = $form->addSelect(
9355
            'previous',
9356
            get_lang('Position'),
9357
            [],
9358
            ['id' => 'previous']
9359
        );
9360
9361
        $position->addOption(get_lang('FirstPosition'), 0);
9362
        foreach ($arrHide as $key => $value) {
9363
            $padding = isset($value['padding']) ? $value['padding'] : 20;
9364
            $position->addOption(
9365
                $value['value'],
9366
                $key,
9367
                'style="padding-left:'.$padding.'px;"'
9368
            );
9369
        }
9370
        $position->setSelected($selectedPosition);
9371
9372
        if (is_array($arrLP)) {
9373
            reset($arrLP);
9374
        }
9375
9376
        if (true !== api_get_configuration_value('lp_item_prerequisite_dates')) {
9377
            $excludeExtraFields = array_merge($excludeExtraFields, ['start_date', 'end_date']);
9378
        }
9379
        $extraField = new ExtraField('lp_item');
9380
        $extraField->addElements($form, $id, $excludeExtraFields);
9381
9382
        if ($action !== 'move') {
9383
            $arrHide = [];
9384
            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...
9385
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] !== 'dir' &&
9386
                    $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9387
                ) {
9388
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9389
                }
9390
            }
9391
9392
            if (!$no_display_add) {
9393
                $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
9394
                $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
9395
                if ($extra_info === 'new' || $item_type == TOOL_DOCUMENT ||
9396
                    $item_type == TOOL_LP_FINAL_ITEM || $edit === 'true'
9397
                ) {
9398
                    if (isset($_POST['content'])) {
9399
                        $content = stripslashes($_POST['content']);
9400
                    } elseif (is_array($extra_info)) {
9401
                        //If it's an html document or a text file
9402
                        if (!$no_display_edit_textarea) {
9403
                            $content = $this->display_document(
9404
                                $extra_info['path'],
9405
                                false,
9406
                                false
9407
                            );
9408
                        }
9409
                    } elseif (is_numeric($extra_info)) {
9410
                        $content = $this->display_document(
9411
                            $extra_info,
9412
                            false,
9413
                            false
9414
                        );
9415
                    } else {
9416
                        $content = '';
9417
                    }
9418
9419
                    if (!$no_display_edit_textarea) {
9420
                        // We need to calculate here some specific settings for the online editor.
9421
                        // The calculated settings work for documents in the Documents tool
9422
                        // (on the root or in subfolders).
9423
                        // For documents in native scorm packages it is unclear whether the
9424
                        // online editor should be activated or not.
9425
9426
                        // A new document, it is in the root of the repository.
9427
                        if (is_array($extra_info) && $extra_info != 'new') {
9428
                            // The document already exists. Whe have to determine its relative path towards the repository root.
9429
                            $relative_path = explode('/', $extra_info['dir']);
9430
                            $cnt = count($relative_path) - 2;
9431
                            if ($cnt < 0) {
9432
                                $cnt = 0;
9433
                            }
9434
                            $relative_prefix = str_repeat('../', $cnt);
9435
                            $relative_path = array_slice($relative_path, 1, $cnt);
9436
                            $relative_path = implode('/', $relative_path);
9437
                            if (strlen($relative_path) > 0) {
9438
                                $relative_path = $relative_path.'/';
9439
                            }
9440
                        } else {
9441
                            $result = $this->generate_lp_folder($_course);
9442
                            $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
9443
                            $relative_prefix = '../../';
9444
                        }
9445
9446
                        $editor_config = [
9447
                            'ToolbarSet' => 'LearningPathDocuments',
9448
                            'Width' => '100%',
9449
                            'Height' => '500',
9450
                            'FullPage' => true,
9451
                            'CreateDocumentDir' => $relative_prefix,
9452
                            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
9453
                            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
9454
                        ];
9455
9456
                        if ($_GET['action'] === 'add_item') {
9457
                            $class = 'add';
9458
                            $text = get_lang('LPCreateDocument');
9459
                        } else {
9460
                            if ($_GET['action'] === 'edit_item') {
9461
                                $class = 'save';
9462
                                $text = get_lang('SaveDocument');
9463
                            }
9464
                        }
9465
9466
                        $form->addButtonSave($text, 'submit_button');
9467
                        $renderer = $form->defaultRenderer();
9468
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
9469
                        $form->addElement('html', '<div class="editor-lp">');
9470
                        $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
9471
                        $form->addElement('html', '</div>');
9472
                        $defaults['content_lp'] = $content;
9473
                    }
9474
                } elseif (is_numeric($extra_info)) {
9475
                    $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9476
9477
                    $return = $this->display_document($extra_info, true, true, true);
9478
                    $form->addElement('html', $return);
9479
                }
9480
            }
9481
        }
9482
        if (isset($extra_info['item_type']) &&
9483
            $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
9484
        ) {
9485
            $parentSelect->freeze();
9486
            $position->freeze();
9487
        }
9488
9489
        if ($action === 'move') {
9490
            $form->addElement('hidden', 'title', $item_title);
9491
            $form->addElement('hidden', 'description', $item_description);
9492
        }
9493
        if (is_numeric($extra_info)) {
9494
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9495
            $form->addElement('hidden', 'path', $extra_info);
9496
        } elseif (is_array($extra_info)) {
9497
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9498
            $form->addElement('hidden', 'path', $extra_info['path']);
9499
        }
9500
9501
        if ($item instanceof learnpathItem) {
9502
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9503
            $form->addElement('hidden', 'path', $item->path);
9504
        }
9505
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
9506
        $form->addElement('hidden', 'post_time', time());
9507
        $this->setAuthorLpItem($form);
9508
        $form->setDefaults($defaults);
9509
9510
        return $form->returnForm();
9511
    }
9512
9513
    /**
9514
     * Returns the form to update or create a read-out text.
9515
     *
9516
     * @param string $action     "add" or "edit"
9517
     * @param int    $id         ID of the lp_item (if already exists)
9518
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
9519
     *
9520
     * @throws Exception
9521
     * @throws HTML_QuickForm_Error
9522
     *
9523
     * @return string HTML form
9524
     */
9525
    public function displayFrmReadOutText($action = 'add', $id = 0, $extra_info = 'new')
9526
    {
9527
        $course_id = api_get_course_int_id();
9528
        $_course = api_get_course_info();
9529
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
9530
9531
        $no_display_edit_textarea = false;
9532
        //If action==edit document
9533
        //We don't display the document form if it's not an editable document (html or txt file)
9534
        if ($action == 'edit') {
9535
            if (is_array($extra_info)) {
9536
                $path_parts = pathinfo($extra_info['dir']);
9537
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
9538
                    $no_display_edit_textarea = true;
9539
                }
9540
            }
9541
        }
9542
        $no_display_add = false;
9543
9544
        $item_title = '';
9545
        $item_description = '';
9546
        if ($id != 0 && is_array($extra_info)) {
9547
            $item_title = stripslashes($extra_info['title']);
9548
            $item_description = stripslashes($extra_info['description']);
9549
            $item_terms = stripslashes($extra_info['terms']);
9550
            if (empty($item_title)) {
9551
                $path_parts = pathinfo($extra_info['path']);
9552
                $item_title = stripslashes($path_parts['filename']);
9553
            }
9554
        } elseif (is_numeric($extra_info)) {
9555
            $sql = "SELECT path, title FROM $tbl_doc WHERE c_id = ".$course_id." AND iid = ".intval($extra_info);
9556
            $result = Database::query($sql);
9557
            $row = Database::fetch_array($result);
9558
            $item_title = $row['title'];
9559
            $item_title = str_replace('_', ' ', $item_title);
9560
            if (empty($item_title)) {
9561
                $path_parts = pathinfo($row['path']);
9562
                $item_title = stripslashes($path_parts['filename']);
9563
            }
9564
        }
9565
9566
        $parent = 0;
9567
        if ($id != 0 && is_array($extra_info)) {
9568
            $parent = $extra_info['parent_item_id'];
9569
        }
9570
9571
        $arrLP = $this->getItemsForForm();
9572
        $this->tree_array($arrLP);
9573
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9574
        unset($this->arrMenu);
9575
9576
        if ($action === 'add') {
9577
            $formHeader = get_lang('CreateTheDocument');
9578
        } else {
9579
            $formHeader = get_lang('EditTheCurrentDocument');
9580
        }
9581
9582
        if ('edit' === $action) {
9583
            $urlAudioIcon = Display::url(
9584
                Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY),
9585
                api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'&'
9586
                    .http_build_query(['view' => 'build', 'id' => $id, 'action' => 'add_audio'])
9587
            );
9588
        } else {
9589
            $urlAudioIcon = Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY);
9590
        }
9591
9592
        $form = new FormValidator(
9593
            'frm_add_reading',
9594
            'POST',
9595
            $this->getCurrentBuildingModeURL(),
9596
            '',
9597
            ['enctype' => 'multipart/form-data']
9598
        );
9599
        $form->addHeader($formHeader);
9600
        $form->addHtml(
9601
            Display::return_message(
9602
                sprintf(get_lang('FrmReadOutTextIntro'), $urlAudioIcon),
9603
                'normal',
9604
                false
9605
            )
9606
        );
9607
        $defaults['title'] = !empty($item_title) ? Security::remove_XSS($item_title) : '';
9608
        $defaults['description'] = $item_description;
9609
9610
        $data = $this->generate_lp_folder($_course);
9611
9612
        if ($action != 'edit') {
9613
            $folders = DocumentManager::get_all_document_folders($_course, 0, true);
9614
            DocumentManager::build_directory_selector(
9615
                $folders,
9616
                '',
9617
                [],
9618
                true,
9619
                $form,
9620
                'directory_parent_id'
9621
            );
9622
        }
9623
9624
        if (isset($data['id'])) {
9625
            $defaults['directory_parent_id'] = $data['id'];
9626
        }
9627
        $this->setItemTitle($form);
9628
9629
        $arrHide[0]['value'] = $this->name;
9630
        $arrHide[0]['padding'] = 20;
9631
9632
        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...
9633
            if ($action != 'add') {
9634
                if ($arrLP[$i]['item_type'] == 'dir' &&
9635
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9636
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9637
                ) {
9638
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9639
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9640
                }
9641
            } else {
9642
                if ($arrLP[$i]['item_type'] == 'dir') {
9643
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9644
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9645
                }
9646
            }
9647
        }
9648
9649
        $parent_select = $form->addSelect(
9650
            'parent',
9651
            get_lang('Parent'),
9652
            [],
9653
            ['onchange' => "javascript: load_cbo(this.value, 'frm_add_reading_previous');"]
9654
        );
9655
9656
        $my_count = 0;
9657
        foreach ($arrHide as $key => $value) {
9658
            if ($my_count != 0) {
9659
                // The LP name is also the first section and is not in the same charset like the other sections.
9660
                $value['value'] = Security::remove_XSS($value['value']);
9661
                $parent_select->addOption(
9662
                    $value['value'],
9663
                    $key,
9664
                    'style="padding-left:'.$value['padding'].'px;"'
9665
                );
9666
            } else {
9667
                $value['value'] = Security::remove_XSS($value['value']);
9668
                $parent_select->addOption(
9669
                    $value['value'],
9670
                    $key,
9671
                    'style="padding-left:'.$value['padding'].'px;"'
9672
                );
9673
            }
9674
            $my_count++;
9675
        }
9676
9677
        if (!empty($id)) {
9678
            $parent_select->setSelected($parent);
9679
        } else {
9680
            $parent_item_id = Session::read('parent_item_id', 0);
9681
            $parent_select->setSelected($parent_item_id);
9682
        }
9683
9684
        if (is_array($arrLP)) {
9685
            reset($arrLP);
9686
        }
9687
9688
        $arrHide = [];
9689
        $s_selected_position = null;
9690
9691
        // POSITION
9692
        $lastPosition = null;
9693
9694
        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...
9695
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) &&
9696
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9697
            ) {
9698
                if ((isset($extra_info['previous_item_id']) &&
9699
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
9700
                ) {
9701
                    $s_selected_position = $arrLP[$i]['id'];
9702
                }
9703
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
9704
            }
9705
            $lastPosition = $arrLP[$i]['id'];
9706
        }
9707
9708
        if (empty($s_selected_position)) {
9709
            $s_selected_position = $lastPosition;
9710
        }
9711
9712
        $position = $form->addSelect(
9713
            'previous',
9714
            get_lang('Position'),
9715
            []
9716
        );
9717
        $position->addOption(get_lang('FirstPosition'), 0);
9718
9719
        foreach ($arrHide as $key => $value) {
9720
            $padding = isset($value['padding']) ? $value['padding'] : 20;
9721
            $position->addOption(
9722
                $value['value'],
9723
                $key,
9724
                'style="padding-left:'.$padding.'px;"'
9725
            );
9726
        }
9727
        $position->setSelected($s_selected_position);
9728
9729
        if (is_array($arrLP)) {
9730
            reset($arrLP);
9731
        }
9732
9733
        if ('edit' === $action) {
9734
            $excludeExtraFields = [];
9735
            if (true !== api_get_configuration_value('lp_item_prerequisite_dates')) {
9736
                $excludeExtraFields = ['start_date', 'end_date'];
9737
            }
9738
            $extraField = new ExtraField('lp_item');
9739
            $extraField->addElements($form, $id, $excludeExtraFields);
9740
        }
9741
9742
        $arrHide = [];
9743
9744
        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...
9745
            if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
9746
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9747
            ) {
9748
                $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9749
            }
9750
        }
9751
9752
        if (!$no_display_add) {
9753
            $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
9754
            $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
9755
9756
            if ($extra_info == 'new' || $item_type == TOOL_READOUT_TEXT || $edit == 'true') {
9757
                if (!$no_display_edit_textarea) {
9758
                    $content = '';
9759
9760
                    if (isset($_POST['content'])) {
9761
                        $content = stripslashes($_POST['content']);
9762
                    } elseif (is_array($extra_info)) {
9763
                        $content = $this->display_document($extra_info['path'], false, false);
9764
                    } elseif (is_numeric($extra_info)) {
9765
                        $content = $this->display_document($extra_info, false, false);
9766
                    }
9767
9768
                    // A new document, it is in the root of the repository.
9769
                    if (is_array($extra_info) && $extra_info != 'new') {
9770
                    } else {
9771
                        $this->generate_lp_folder($_course);
9772
                    }
9773
9774
                    if ($_GET['action'] == 'add_item') {
9775
                        $text = get_lang('LPCreateDocument');
9776
                    } else {
9777
                        $text = get_lang('SaveDocument');
9778
                    }
9779
9780
                    $form->addTextarea('content_lp', get_lang('Content'), ['rows' => 20]);
9781
                    $form
9782
                        ->defaultRenderer()
9783
                        ->setElementTemplate($form->getDefaultElementTemplate(), 'content_lp');
9784
                    $form->addButtonSave($text, 'submit_button');
9785
                    $defaults['content_lp'] = $content;
9786
                }
9787
            } elseif (is_numeric($extra_info)) {
9788
                $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9789
9790
                $return = $this->display_document($extra_info, true, true, true);
9791
                $form->addElement('html', $return);
9792
            }
9793
        }
9794
9795
        if (is_numeric($extra_info)) {
9796
            $form->addElement('hidden', 'path', $extra_info);
9797
        } elseif (is_array($extra_info)) {
9798
            $form->addElement('hidden', 'path', $extra_info['path']);
9799
        }
9800
9801
        $form->addElement('hidden', 'type', TOOL_READOUT_TEXT);
9802
        $form->addElement('hidden', 'post_time', time());
9803
        $this->setAuthorLpItem($form);
9804
        $form->setDefaults($defaults);
9805
9806
        return $form->returnForm();
9807
    }
9808
9809
    /**
9810
     * @param array  $courseInfo
9811
     * @param string $content
9812
     * @param string $title
9813
     * @param int    $parentId
9814
     *
9815
     * @throws \Doctrine\ORM\ORMException
9816
     * @throws \Doctrine\ORM\OptimisticLockException
9817
     * @throws \Doctrine\ORM\TransactionRequiredException
9818
     *
9819
     * @return int
9820
     */
9821
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
9822
    {
9823
        $creatorId = api_get_user_id();
9824
        $sessionId = api_get_session_id();
9825
9826
        // Generates folder
9827
        $result = $this->generate_lp_folder($courseInfo);
9828
        $dir = $result['dir'];
9829
9830
        if (empty($parentId) || $parentId == '/') {
9831
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
9832
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
9833
9834
            if ($parentId === '/') {
9835
                $dir = '/';
9836
            }
9837
9838
            // Please, do not modify this dirname formatting.
9839
            if (strstr($dir, '..')) {
9840
                $dir = '/';
9841
            }
9842
9843
            if (!empty($dir[0]) && $dir[0] == '.') {
9844
                $dir = substr($dir, 1);
9845
            }
9846
            if (!empty($dir[0]) && $dir[0] != '/') {
9847
                $dir = '/'.$dir;
9848
            }
9849
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
9850
                $dir .= '/';
9851
            }
9852
        } else {
9853
            $parentInfo = DocumentManager::get_document_data_by_id(
9854
                $parentId,
9855
                $courseInfo['code']
9856
            );
9857
            if (!empty($parentInfo)) {
9858
                $dir = $parentInfo['path'].'/';
9859
            }
9860
        }
9861
9862
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9863
9864
        if (!is_dir($filepath)) {
9865
            $dir = '/';
9866
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9867
        }
9868
9869
        $originalTitle = !empty($title) ? $title : $_POST['title'];
9870
9871
        if (!empty($title)) {
9872
            $title = api_replace_dangerous_char(stripslashes($title));
9873
        } else {
9874
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
9875
        }
9876
9877
        $title = disable_dangerous_file($title);
9878
        $filename = $title;
9879
        $content = !empty($content) ? $content : $_POST['content_lp'];
9880
        $tmpFileName = $filename;
9881
9882
        $i = 0;
9883
        while (file_exists($filepath.$tmpFileName.'.html')) {
9884
            $tmpFileName = $filename.'_'.++$i;
9885
        }
9886
9887
        $filename = $tmpFileName.'.html';
9888
        $content = stripslashes($content);
9889
9890
        if (file_exists($filepath.$filename)) {
9891
            return 0;
9892
        }
9893
9894
        $putContent = file_put_contents($filepath.$filename, $content);
9895
9896
        if ($putContent === false) {
9897
            return 0;
9898
        }
9899
9900
        $fileSize = filesize($filepath.$filename);
9901
        $saveFilePath = $dir.$filename;
9902
9903
        $documentId = add_document(
9904
            $courseInfo,
9905
            $saveFilePath,
9906
            'file',
9907
            $fileSize,
9908
            $tmpFileName,
9909
            '',
9910
            0, //readonly
9911
            true,
9912
            null,
9913
            $sessionId,
9914
            $creatorId
9915
        );
9916
9917
        if (!$documentId) {
9918
            return 0;
9919
        }
9920
9921
        api_item_property_update(
9922
            $courseInfo,
9923
            TOOL_DOCUMENT,
9924
            $documentId,
9925
            'DocumentAdded',
9926
            $creatorId,
9927
            null,
9928
            null,
9929
            null,
9930
            null,
9931
            $sessionId
9932
        );
9933
9934
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
9935
        $newTitle = $originalTitle;
9936
9937
        if ($newComment || $newTitle) {
9938
            $em = Database::getManager();
9939
9940
            /** @var CDocument $doc */
9941
            $doc = $em->find('ChamiloCourseBundle:CDocument', $documentId);
9942
9943
            if ($newComment) {
9944
                $doc->setComment($newComment);
9945
            }
9946
9947
            if ($newTitle) {
9948
                $doc->setTitle($newTitle);
9949
            }
9950
9951
            $em->persist($doc);
9952
            $em->flush();
9953
        }
9954
9955
        return $documentId;
9956
    }
9957
9958
    /**
9959
     * Return HTML form to add/edit a link item.
9960
     *
9961
     * @param string $action     (add/edit)
9962
     * @param int    $id         Item ID if exists
9963
     * @param mixed  $extra_info
9964
     *
9965
     * @throws Exception
9966
     * @throws HTML_QuickForm_Error
9967
     *
9968
     * @return string HTML form
9969
     */
9970
    public function display_link_form(
9971
        $action = 'add',
9972
        $id = 0,
9973
        $extra_info = '',
9974
        $item = null,
9975
        $excludeExtraFields = []
9976
    ) {
9977
        $course_id = api_get_course_int_id();
9978
        $tbl_link = Database::get_course_table(TABLE_LINK);
9979
9980
        $item_title = '';
9981
        $item_description = '';
9982
        $item_url = '';
9983
9984
        $previousId = 0;
9985
        if ($id != 0 && is_array($extra_info)) {
9986
            $item_title = stripslashes($extra_info['title']);
9987
            $item_description = stripslashes($extra_info['description']);
9988
            $item_url = stripslashes($extra_info['url']);
9989
            $previousId = $extra_info['previous_item_id'];
9990
        } elseif (is_numeric($extra_info)) {
9991
            $extra_info = (int) $extra_info;
9992
            $sql = "SELECT title, description, url
9993
                    FROM $tbl_link
9994
                    WHERE c_id = $course_id AND iid = $extra_info";
9995
            $result = Database::query($sql);
9996
            $row = Database::fetch_array($result);
9997
            $item_title = $row['title'];
9998
            $item_description = $row['description'];
9999
            $item_url = $row['url'];
10000
        }
10001
10002
        if ($item instanceof learnpathItem) {
10003
            $previousId = $extra_info->previous;
10004
        }
10005
10006
        $form = new FormValidator(
10007
            'edit_link',
10008
            'POST',
10009
            $this->getCurrentBuildingModeURL()
10010
        );
10011
        $defaults = [];
10012
        $parent = 0;
10013
        if ($id != 0 && is_array($extra_info)) {
10014
            $parent = $extra_info['parent_item_id'];
10015
        }
10016
10017
        $arrLP = $this->getItemsForForm();
10018
10019
        $this->tree_array($arrLP);
10020
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
10021
        unset($this->arrMenu);
10022
10023
        if ($action === 'add') {
10024
            $legend = get_lang('CreateTheLink');
10025
        } elseif ($action === 'move') {
10026
            $legend = get_lang('MoveCurrentLink');
10027
        } else {
10028
            $legend = get_lang('EditCurrentLink');
10029
        }
10030
10031
        $form->addHeader($legend);
10032
10033
        if ($action !== 'move') {
10034
            $this->setItemTitle($form);
10035
            $defaults['title'] = $item_title;
10036
        }
10037
10038
        $selectParent = $form->addSelect(
10039
            'parent',
10040
            get_lang('Parent'),
10041
            [],
10042
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
10043
        );
10044
        $selectParent->addOption($this->name, 0);
10045
        $arrHide = [
10046
            $id,
10047
        ];
10048
10049
        $parent_item_id = Session::read('parent_item_id', 0);
10050
10051
        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...
10052
            if ($action != 'add') {
10053
                if (
10054
                    ($arrLP[$i]['item_type'] == 'dir') &&
10055
                    !in_array($arrLP[$i]['id'], $arrHide) &&
10056
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
10057
                ) {
10058
                    $selectParent->addOption(
10059
                        $arrLP[$i]['title'],
10060
                        $arrLP[$i]['id'],
10061
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
10062
                    );
10063
10064
                    if ($parent == $arrLP[$i]['id']) {
10065
                        $selectParent->setSelected($arrLP[$i]['id']);
10066
                    }
10067
                } else {
10068
                    $arrHide[] = $arrLP[$i]['id'];
10069
                }
10070
            } else {
10071
                if ($arrLP[$i]['item_type'] == 'dir') {
10072
                    $selectParent->addOption(
10073
                        $arrLP[$i]['title'],
10074
                        $arrLP[$i]['id'],
10075
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
10076
                    );
10077
10078
                    if ($parent_item_id == $arrLP[$i]['id']) {
10079
                        $selectParent->setSelected($arrLP[$i]['id']);
10080
                    }
10081
                }
10082
            }
10083
        }
10084
10085
        if (is_array($arrLP)) {
10086
            reset($arrLP);
10087
        }
10088
10089
        $selectPrevious = $form->addSelect(
10090
            'previous',
10091
            get_lang('Position'),
10092
            [],
10093
            ['id' => 'previous', 'class' => 'learnpath_item_form']
10094
        );
10095
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
10096
10097
        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...
10098
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
10099
                $selectPrevious->addOption(
10100
                    $arrLP[$i]['title'],
10101
                    $arrLP[$i]['id']
10102
                );
10103
10104
                if ($previousId == $arrLP[$i]['id']) {
10105
                    $selectPrevious->setSelected($arrLP[$i]['id']);
10106
                } elseif ($action === 'add') {
10107
                    $selectPrevious->setSelected($arrLP[$i]['id']);
10108
                }
10109
            }
10110
        }
10111
10112
        if ($action !== 'move') {
10113
            $urlAttributes = ['class' => 'learnpath_item_form'];
10114
10115
            if (is_numeric($extra_info)) {
10116
                $urlAttributes['disabled'] = 'disabled';
10117
            }
10118
10119
            $form->addElement('url', 'url', get_lang('Url'), $urlAttributes);
10120
            $defaults['url'] = $item_url;
10121
            $arrHide = [];
10122
            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...
10123
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
10124
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
10125
                }
10126
            }
10127
        }
10128
10129
        if ('edit' === $action) {
10130
            if (true !== api_get_configuration_value('lp_item_prerequisite_dates')) {
10131
                $excludeExtraFields = array_merge($excludeExtraFields, ['start_date', 'end_date']);
10132
            }
10133
            $extraField = new ExtraField('lp_item');
10134
            $extraField->addElements($form, $id, $excludeExtraFields);
10135
        }
10136
10137
        if ($action === 'add') {
10138
            $form->addButtonSave(get_lang('AddLinkToCourse'), 'submit_button');
10139
        } else {
10140
            $form->addButtonSave(get_lang('EditCurrentLink'), 'submit_button');
10141
        }
10142
10143
        if ($action === 'move') {
10144
            $form->addHidden('title', $item_title);
10145
            $form->addHidden('description', $item_description);
10146
        }
10147
10148
        if (is_numeric($extra_info)) {
10149
            $form->addHidden('path', $extra_info);
10150
        } elseif (is_array($extra_info)) {
10151
            $form->addHidden('path', $extra_info['path']);
10152
        }
10153
        $form->addHidden('type', TOOL_LINK);
10154
        $form->addHidden('post_time', time());
10155
        $this->setAuthorLpItem($form);
10156
        $form->setDefaults($defaults);
10157
10158
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
10159
    }
10160
10161
    /**
10162
     * Return HTML form to add/edit a student publication (work).
10163
     *
10164
     * @param string $action
10165
     * @param int    $id         Item ID if already exists
10166
     * @param string $extra_info
10167
     *
10168
     * @throws Exception
10169
     *
10170
     * @return string HTML form
10171
     */
10172
    public function display_student_publication_form(
10173
        $action = 'add',
10174
        $id = 0,
10175
        $extra_info = '',
10176
        $item = null,
10177
        $excludeExtraFields = []
10178
    ) {
10179
        $course_id = api_get_course_int_id();
10180
        $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
10181
10182
        $item_title = get_lang('Student_publication');
10183
        $previousId = 0;
10184
        if ($id != 0 && is_array($extra_info)) {
10185
            $item_title = stripslashes($extra_info['title']);
10186
            $item_description = stripslashes($extra_info['description']);
10187
            $previousId = $extra_info['previous_item_id'];
10188
        } elseif (is_numeric($extra_info)) {
10189
            $extra_info = (int) $extra_info;
10190
            $sql = "SELECT title, description
10191
                    FROM $tbl_publication
10192
                    WHERE c_id = $course_id AND id = ".$extra_info;
10193
10194
            $result = Database::query($sql);
10195
            $row = Database::fetch_array($result);
10196
            if ($row) {
10197
                $item_title = $row['title'];
10198
            }
10199
        }
10200
10201
        if ($item instanceof learnpathItem) {
10202
            $item_title = $item->get_title();
10203
            $item_description = $item->get_description();
10204
            $previousId = $item->previous;
10205
        }
10206
10207
        $parent = 0;
10208
        if ($id != 0 && is_array($extra_info)) {
10209
            $parent = $extra_info['parent_item_id'];
10210
        }
10211
10212
        $arrLP = $this->getItemsForForm();
10213
10214
        $this->tree_array($arrLP);
10215
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
10216
        unset($this->arrMenu);
10217
10218
        $form = new FormValidator('frm_student_publication', 'post', '#');
10219
10220
        if ($action === 'add') {
10221
            $form->addHeader(get_lang('Student_publication'));
10222
        } elseif ($action === 'move') {
10223
            $form->addHeader(get_lang('MoveCurrentStudentPublication'));
10224
        } else {
10225
            $form->addHeader(get_lang('EditCurrentStudentPublication'));
10226
        }
10227
10228
        if ($action !== 'move') {
10229
            $this->setItemTitle($form);
10230
        }
10231
10232
        $parentSelect = $form->addSelect(
10233
            'parent',
10234
            get_lang('Parent'),
10235
            ['0' => $this->name],
10236
            [
10237
                'onchange' => 'javascript: load_cbo(this.value);',
10238
                'class' => 'learnpath_item_form',
10239
                'id' => 'idParent',
10240
            ]
10241
        );
10242
10243
        $arrHide = [$id];
10244
        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...
10245
            if ($action != 'add') {
10246
                if (
10247
                    ($arrLP[$i]['item_type'] == 'dir') &&
10248
                    !in_array($arrLP[$i]['id'], $arrHide) &&
10249
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
10250
                ) {
10251
                    $parentSelect->addOption(
10252
                        $arrLP[$i]['title'],
10253
                        $arrLP[$i]['id'],
10254
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
10255
                    );
10256
10257
                    if ($parent == $arrLP[$i]['id']) {
10258
                        $parentSelect->setSelected($arrLP[$i]['id']);
10259
                    }
10260
                } else {
10261
                    $arrHide[] = $arrLP[$i]['id'];
10262
                }
10263
            } else {
10264
                if ($arrLP[$i]['item_type'] === 'dir') {
10265
                    $parentSelect->addOption(
10266
                        $arrLP[$i]['title'],
10267
                        $arrLP[$i]['id'],
10268
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
10269
                    );
10270
10271
                    if ($parent == $arrLP[$i]['id']) {
10272
                        $parentSelect->setSelected($arrLP[$i]['id']);
10273
                    }
10274
                }
10275
            }
10276
        }
10277
10278
        if (is_array($arrLP)) {
10279
            reset($arrLP);
10280
        }
10281
10282
        $previousSelect = $form->addSelect(
10283
            'previous',
10284
            get_lang('Position'),
10285
            ['0' => get_lang('FirstPosition')],
10286
            ['id' => 'previous', 'class' => 'learnpath_item_form']
10287
        );
10288
10289
        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...
10290
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
10291
                $previousSelect->addOption(
10292
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
10293
                    $arrLP[$i]['id']
10294
                );
10295
10296
                if ($previousId == $arrLP[$i]['id']) {
10297
                    $previousSelect->setSelected($arrLP[$i]['id']);
10298
                } elseif ($action === 'add') {
10299
                    $previousSelect->setSelected($arrLP[$i]['id']);
10300
                }
10301
            }
10302
        }
10303
10304
        if ('edit' === $action) {
10305
            if (true !== api_get_configuration_value('lp_item_prerequisite_dates')) {
10306
                $excludeExtraFields = array_merge($excludeExtraFields, ['start_date', 'end_date']);
10307
            }
10308
            $extraField = new ExtraField('lp_item');
10309
            $extraField->addElements($form, $id, $excludeExtraFields);
10310
        }
10311
10312
        if ($action === 'add') {
10313
            $form->addButtonCreate(get_lang('AddAssignmentToCourse'), 'submit_button');
10314
        } else {
10315
            $form->addButtonCreate(get_lang('EditCurrentStudentPublication'), 'submit_button');
10316
        }
10317
10318
        if ($action === 'move') {
10319
            $form->addHidden('title', $item_title);
10320
            $form->addHidden('description', $item_description);
10321
        }
10322
10323
        if (is_numeric($extra_info)) {
10324
            $form->addHidden('path', $extra_info);
10325
        } elseif (is_array($extra_info)) {
10326
            $form->addHidden('path', $extra_info['path']);
10327
        }
10328
10329
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
10330
        $form->addHidden('post_time', time());
10331
        $this->setAuthorLpItem($form);
10332
        $form->setDefaults(['title' => $item_title, 'start_date' => null]);
10333
10334
        $return = '<div class="sectioncomment">';
10335
        $return .= $form->returnForm();
10336
        $return .= '</div>';
10337
10338
        return $return;
10339
    }
10340
10341
    /**
10342
     * Displays the menu for manipulating a step.
10343
     *
10344
     * @param int    $item_id
10345
     * @param string $item_type
10346
     *
10347
     * @return string
10348
     */
10349
    public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
10350
    {
10351
        $_course = api_get_course_info();
10352
        $course_code = api_get_course_id();
10353
        $item_id = (int) $item_id;
10354
10355
        $return = '<div class="actions">';
10356
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10357
        $sql = "SELECT * FROM $tbl_lp_item
10358
                WHERE iid = ".$item_id;
10359
        $result = Database::query($sql);
10360
        $row = Database::fetch_assoc($result);
10361
10362
        $audio_player = null;
10363
        // We display an audio player if needed.
10364
        if (!empty($row['audio'])) {
10365
            $audio = learnpathItem::fixAudio($row['audio']);
10366
            $webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document'.$audio;
10367
            $audio_player .= '<div class="lp_mediaplayer" id="container">
10368
                            <audio src="'.$webAudioPath.'" controls>
10369
                            </div><br />';
10370
        }
10371
10372
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
10373
10374
        if ($item_type != TOOL_LP_FINAL_ITEM) {
10375
            $return .= Display::url(
10376
                Display::return_icon(
10377
                    'edit.png',
10378
                    get_lang('Edit'),
10379
                    [],
10380
                    ICON_SIZE_SMALL
10381
                ),
10382
                $url.'&action=edit_item&path_item='.$row['path']
10383
            );
10384
10385
            $return .= Display::url(
10386
                Display::return_icon(
10387
                    'move.png',
10388
                    get_lang('Move'),
10389
                    [],
10390
                    ICON_SIZE_SMALL
10391
                ),
10392
                $url.'&action=move_item'
10393
            );
10394
        }
10395
10396
        // Commented for now as prerequisites cannot be added to chapters.
10397
        if ($item_type != 'dir') {
10398
            $return .= Display::url(
10399
                Display::return_icon(
10400
                    'accept.png',
10401
                    get_lang('LearnpathPrerequisites'),
10402
                    [],
10403
                    ICON_SIZE_SMALL
10404
                ),
10405
                $url.'&action=edit_item_prereq'
10406
            );
10407
        }
10408
        $return .= Display::url(
10409
            Display::return_icon(
10410
                'delete.png',
10411
                get_lang('Delete'),
10412
                [],
10413
                ICON_SIZE_SMALL
10414
            ),
10415
            $url.'&action=delete_item'
10416
        );
10417
10418
        if (in_array($item_type, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
10419
            $documentData = DocumentManager::get_document_data_by_id($row['path'], $course_code);
10420
            if (empty($documentData)) {
10421
                // Try with iid
10422
                $table = Database::get_course_table(TABLE_DOCUMENT);
10423
                $sql = "SELECT path FROM $table
10424
                        WHERE
10425
                              c_id = ".api_get_course_int_id()." AND
10426
                              iid = ".$row['path']." AND
10427
                              path NOT LIKE '%_DELETED_%'";
10428
                $result = Database::query($sql);
10429
                $documentData = Database::fetch_array($result);
10430
                if ($documentData) {
10431
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
10432
                }
10433
            }
10434
            if (isset($documentData['absolute_path_from_document'])) {
10435
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
10436
            }
10437
        }
10438
10439
        $return .= '</div>';
10440
10441
        if (!empty($audio_player)) {
10442
            $return .= $audio_player;
10443
        }
10444
10445
        return $return;
10446
    }
10447
10448
    /**
10449
     * Creates the javascript needed for filling up the checkboxes without page reload.
10450
     *
10451
     * @return string
10452
     */
10453
    public function get_js_dropdown_array()
10454
    {
10455
        $course_id = api_get_course_int_id();
10456
        $return = 'var child_name = new Array();'."\n";
10457
        $return .= 'var child_value = new Array();'."\n\n";
10458
        $return .= 'child_name[0] = new Array();'."\n";
10459
        $return .= 'child_value[0] = new Array();'."\n\n";
10460
10461
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10462
        $i = 0;
10463
        $list = $this->getItemsForForm(true);
10464
10465
        foreach ($list as $row_zero) {
10466
            if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
10467
                if ($row_zero['item_type'] == TOOL_QUIZ) {
10468
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
10469
                }
10470
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
10471
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
10472
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
10473
            }
10474
        }
10475
10476
        $return .= "\n";
10477
        $sql = "SELECT * FROM $tbl_lp_item
10478
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
10479
        $res = Database::query($sql);
10480
        while ($row = Database::fetch_array($res)) {
10481
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
10482
                           WHERE
10483
                                c_id = ".$course_id." AND
10484
                                parent_item_id = ".$row['iid']."
10485
                           ORDER BY display_order ASC";
10486
            $res_parent = Database::query($sql_parent);
10487
            $i = 0;
10488
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
10489
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
10490
10491
            while ($row_parent = Database::fetch_array($res_parent)) {
10492
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
10493
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
10494
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
10495
            }
10496
            $return .= "\n";
10497
        }
10498
10499
        $return .= "
10500
            function load_cbo(id) {
10501
                if (!id) {
10502
                    return false;
10503
                }
10504
10505
                var cbo = document.getElementById('previous');
10506
                for (var i = cbo.length - 1; i > 0; i--) {
10507
                    cbo.options[i] = null;
10508
                }
10509
10510
                var k=0;
10511
                for(var i = 1; i <= child_name[id].length; i++){
10512
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
10513
                    option.style.paddingLeft = '40px';
10514
                    cbo.options[i] = option;
10515
                    k = i;
10516
                }
10517
10518
                cbo.options[k].selected = true;
10519
                $('#previous').selectpicker('refresh');
10520
            }";
10521
10522
        return $return;
10523
    }
10524
10525
    /**
10526
     * Display the form to allow moving an item.
10527
     *
10528
     * @param learnpathItem $item Item ID
10529
     *
10530
     * @throws Exception
10531
     * @throws HTML_QuickForm_Error
10532
     *
10533
     * @return string HTML form
10534
     */
10535
    public function display_move_item($item)
10536
    {
10537
        $return = '';
10538
        if ($item) {
10539
            $item_id = $item->getIid();
10540
            $type = $item->get_type();
10541
            $path = (int) $item->get_path();
10542
10543
            switch ($type) {
10544
                case 'dir':
10545
                case 'asset':
10546
                    $return .= $this->display_manipulate($item_id, $type);
10547
                    $return .= $this->display_item_form(
10548
                        $type,
10549
                        get_lang('MoveCurrentChapter'),
10550
                        'move',
10551
                        $item_id,
10552
                        $item
10553
                    );
10554
                    break;
10555
                case TOOL_DOCUMENT:
10556
                    $return .= $this->display_manipulate($item_id, $type);
10557
                    $return .= $this->display_document_form('move', $item_id, null, $item);
10558
                    break;
10559
                case TOOL_LINK:
10560
                    $return .= $this->display_manipulate($item_id, $type);
10561
                    $return .= $this->display_link_form('move', $item_id, $path, $item);
10562
                    break;
10563
                case TOOL_HOTPOTATOES:
10564
                    $return .= $this->display_manipulate($item_id, $type);
10565
                    $return .= $this->display_link_form('move', $item_id, $item);
10566
                    break;
10567
                case TOOL_QUIZ:
10568
                    $return .= $this->display_manipulate($item_id, $type);
10569
                    $return .= $this->display_quiz_form('move', $item_id, $item);
10570
                    break;
10571
                case TOOL_STUDENTPUBLICATION:
10572
                    $return .= $this->display_manipulate($item_id, $type);
10573
                    $return .= $this->display_student_publication_form('move', $item_id, $path, $item);
10574
                    break;
10575
                case TOOL_FORUM:
10576
                    $return .= $this->display_manipulate($item_id, $type);
10577
                    $return .= $this->display_forum_form('move', $item_id, $path);
10578
                    break;
10579
                case TOOL_THREAD:
10580
                    $return .= $this->display_manipulate($item_id, $type);
10581
                    $return .= $this->display_forum_form('move', $item_id, $path);
10582
                    break;
10583
            }
10584
        }
10585
10586
        return $return;
10587
    }
10588
10589
    /**
10590
     * Return HTML form to allow prerequisites selection.
10591
     *
10592
     * @todo use FormValidator
10593
     *
10594
     * @param int Item ID
10595
     *
10596
     * @return string HTML form
10597
     */
10598
    public function display_item_prerequisites_form($item_id = 0)
10599
    {
10600
        $course_id = api_get_course_int_id();
10601
        $item_id = (int) $item_id;
10602
10603
        if (empty($item_id)) {
10604
            return '';
10605
        }
10606
10607
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10608
10609
        /* Current prerequisite */
10610
        $sql = "SELECT * FROM $tbl_lp_item
10611
                WHERE iid = $item_id";
10612
        $result = Database::query($sql);
10613
        $row = Database::fetch_array($result);
10614
        $prerequisiteId = $row['prerequisite'];
10615
10616
        $return = '<legend>';
10617
        $return .= get_lang('AddEditPrerequisites');
10618
        $return .= '</legend>';
10619
        $return .= '<form method="POST">';
10620
        $return .= '<div class="table-responsive">';
10621
        $return .= '<table class="table table-hover">';
10622
        $return .= '<thead>';
10623
        $return .= '<tr>';
10624
        $return .= '<th>'.get_lang('LearnpathPrerequisites').'</th>';
10625
        $return .= '<th width="140">'.get_lang('Minimum').'</th>';
10626
        $return .= '<th width="140">'.get_lang('Maximum').'</th>';
10627
        $return .= '</tr>';
10628
        $return .= '</thead>';
10629
10630
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
10631
        $return .= '<tbody>';
10632
        $return .= '<tr>';
10633
        $return .= '<td colspan="3">';
10634
        $return .= '<div class="radio learnpath"><label for="idNone">';
10635
        $return .= '<input checked="checked" id="idNone" name="prerequisites" type="radio" />';
10636
        $return .= get_lang('None').'</label>';
10637
        $return .= '</div>';
10638
        $return .= '</tr>';
10639
10640
        $sql = "SELECT * FROM $tbl_lp_item
10641
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
10642
        $result = Database::query($sql);
10643
10644
        $selectedMinScore = [];
10645
        $selectedMaxScore = [];
10646
        $masteryScore = [];
10647
        while ($row = Database::fetch_array($result)) {
10648
            if ($row['iid'] == $item_id) {
10649
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
10650
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
10651
            }
10652
            $masteryScore[$row['iid']] = $row['mastery_score'];
10653
        }
10654
10655
        $arrLP = $this->getItemsForForm();
10656
        $this->tree_array($arrLP);
10657
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
10658
        unset($this->arrMenu);
10659
10660
        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...
10661
            $item = $arrLP[$i];
10662
10663
            if ($item['id'] == $item_id) {
10664
                break;
10665
            }
10666
10667
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
10668
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
10669
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
10670
10671
            $return .= '<tr>';
10672
            $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
10673
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
10674
            $return .= '<label for="id'.$item['id'].'">';
10675
10676
            $checked = '';
10677
            if (null !== $prerequisiteId) {
10678
                $checked = in_array($prerequisiteId, [$item['id'], $item['ref']]) ? ' checked="checked" ' : '';
10679
            }
10680
10681
            $disabled = $item['item_type'] === 'dir' ? ' disabled="disabled" ' : '';
10682
10683
            $return .= '<input '.$checked.' '.$disabled.' id="id'.$item['id'].'" name="prerequisites" type="radio" value="'.$item['id'].'" />';
10684
10685
            $icon_name = str_replace(' ', '', $item['item_type']);
10686
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
10687
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
10688
            } else {
10689
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
10690
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
10691
                } else {
10692
                    $return .= Display::return_icon('folder_document.png');
10693
                }
10694
            }
10695
10696
            $return .= $item['title'].'</label>';
10697
            $return .= '</div>';
10698
            $return .= '</td>';
10699
10700
            if ($item['item_type'] == TOOL_QUIZ) {
10701
                // lets update max_score Quiz information depending of the Quiz Advanced properties
10702
                $lpItemObj = new LpItem($course_id, $item['id']);
10703
                $exercise = new Exercise($course_id);
10704
                $exercise->read($lpItemObj->path);
10705
                $lpItemObj->max_score = $exercise->get_max_score();
10706
                $lpItemObj->update();
10707
                $item['max_score'] = $lpItemObj->max_score;
10708
10709
                //if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
10710
                if (!isset($selectedMinScore[$item['id']]) && !empty($masteryScoreAsMinValue)) {
10711
                    // Backwards compatibility with 1.9.x use mastery_score as min value
10712
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
10713
                }
10714
10715
                $return .= '<td>';
10716
                $return .= '<input
10717
                    class="form-control"
10718
                    size="4" maxlength="3"
10719
                    name="min_'.$item['id'].'"
10720
                    type="number"
10721
                    min="0"
10722
                    step="any"
10723
                    max="'.$item['max_score'].'"
10724
                    value="'.$selectedMinScoreValue.'"
10725
                />';
10726
                $return .= '</td>';
10727
                $return .= '<td>';
10728
                $return .= '<input
10729
                    class="form-control"
10730
                    size="4"
10731
                    maxlength="3"
10732
                    name="max_'.$item['id'].'"
10733
                    type="number"
10734
                    min="0"
10735
                    step="any"
10736
                    max="'.$item['max_score'].'"
10737
                    value="'.$selectedMaxScoreValue.'"
10738
                />';
10739
                $return .= '</td>';
10740
            }
10741
10742
            if ($item['item_type'] == TOOL_HOTPOTATOES) {
10743
                $return .= '<td>';
10744
                $return .= '<input
10745
                    size="4"
10746
                    maxlength="3"
10747
                    name="min_'.$item['id'].'"
10748
                    type="number"
10749
                    min="0"
10750
                    step="any"
10751
                    max="'.$item['max_score'].'"
10752
                    value="'.$selectedMinScoreValue.'"
10753
                />';
10754
                $return .= '</td>';
10755
                $return .= '<td>';
10756
                $return .= '<input
10757
                    size="4"
10758
                    maxlength="3"
10759
                    name="max_'.$item['id'].'"
10760
                    type="number"
10761
                    min="0"
10762
                    step="any"
10763
                    max="'.$item['max_score'].'"
10764
                    value="'.$selectedMaxScoreValue.'"
10765
                />';
10766
                $return .= '</td>';
10767
            }
10768
            $return .= '</tr>';
10769
        }
10770
        $return .= '<tr>';
10771
        $return .= '</tr>';
10772
        $return .= '</tbody>';
10773
        $return .= '</table>';
10774
        $return .= '</div>';
10775
        $return .= '<div class="form-group">';
10776
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
10777
            get_lang('ModifyPrerequisites').'</button>';
10778
        $return .= '</form>';
10779
10780
        return $return;
10781
    }
10782
10783
    /**
10784
     * Return HTML list to allow prerequisites selection for lp.
10785
     *
10786
     * @return string HTML form
10787
     */
10788
    public function display_lp_prerequisites_list()
10789
    {
10790
        $course_id = api_get_course_int_id();
10791
        $lp_id = $this->lp_id;
10792
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
10793
10794
        // get current prerequisite
10795
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
10796
        $result = Database::query($sql);
10797
        $row = Database::fetch_array($result);
10798
        $prerequisiteId = $row['prerequisite'];
10799
        $session_id = api_get_session_id();
10800
        $session_condition = api_get_session_condition($session_id, true, true);
10801
        $sql = "SELECT * FROM $tbl_lp
10802
                WHERE c_id = $course_id $session_condition
10803
                ORDER BY display_order ";
10804
        $rs = Database::query($sql);
10805
        $return = '';
10806
        $return .= '<select name="prerequisites" class="form-control">';
10807
        $return .= '<option value="0">'.get_lang('None').'</option>';
10808
        if (Database::num_rows($rs) > 0) {
10809
            while ($row = Database::fetch_array($rs)) {
10810
                if ($row['id'] == $lp_id) {
10811
                    continue;
10812
                }
10813
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
10814
            }
10815
        }
10816
        $return .= '</select>';
10817
10818
        return $return;
10819
    }
10820
10821
    /**
10822
     * Creates a list with all the documents in it.
10823
     *
10824
     * @param bool $showInvisibleFiles
10825
     *
10826
     * @throws Exception
10827
     * @throws HTML_QuickForm_Error
10828
     *
10829
     * @return string
10830
     */
10831
    public function get_documents($showInvisibleFiles = false)
10832
    {
10833
        $course_info = api_get_course_info();
10834
        $sessionId = api_get_session_id();
10835
        $documentTree = DocumentManager::get_document_preview(
10836
            $course_info,
10837
            $this->lp_id,
10838
            null,
10839
            $sessionId,
10840
            true,
10841
            null,
10842
            null,
10843
            $showInvisibleFiles,
10844
            true
10845
        );
10846
10847
        $headers = [
10848
            get_lang('Files'),
10849
            get_lang('CreateTheDocument'),
10850
            get_lang('CreateReadOutText'),
10851
            get_lang('Upload'),
10852
        ];
10853
10854
        $form = new FormValidator(
10855
            'form_upload',
10856
            'POST',
10857
            $this->getCurrentBuildingModeURL(),
10858
            '',
10859
            ['enctype' => 'multipart/form-data']
10860
        );
10861
10862
        $folders = DocumentManager::get_all_document_folders(
10863
            api_get_course_info(),
10864
            0,
10865
            true
10866
        );
10867
10868
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
10869
10870
        DocumentManager::build_directory_selector(
10871
            $folders,
10872
            $lpPathInfo['id'],
10873
            [],
10874
            true,
10875
            $form,
10876
            'directory_parent_id'
10877
        );
10878
10879
        $group = [
10880
            $form->createElement(
10881
                'radio',
10882
                'if_exists',
10883
                get_lang('UplWhatIfFileExists'),
10884
                get_lang('UplDoNothing'),
10885
                'nothing'
10886
            ),
10887
            $form->createElement(
10888
                'radio',
10889
                'if_exists',
10890
                null,
10891
                get_lang('UplOverwriteLong'),
10892
                'overwrite'
10893
            ),
10894
            $form->createElement(
10895
                'radio',
10896
                'if_exists',
10897
                null,
10898
                get_lang('UplRenameLong'),
10899
                'rename'
10900
            ),
10901
        ];
10902
        $form->addGroup($group, null, get_lang('UplWhatIfFileExists'));
10903
10904
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
10905
        $defaultFileExistsOption = 'rename';
10906
        if (!empty($fileExistsOption)) {
10907
            $defaultFileExistsOption = $fileExistsOption;
10908
        }
10909
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
10910
10911
        // Check box options
10912
        $form->addElement(
10913
            'checkbox',
10914
            'unzip',
10915
            get_lang('Options'),
10916
            get_lang('Uncompress')
10917
        );
10918
10919
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
10920
        $form->addMultipleUpload($url);
10921
        $new = $this->display_document_form('add', 0);
10922
        $frmReadOutText = $this->displayFrmReadOutText('add');
10923
        $tabs = Display::tabs(
10924
            $headers,
10925
            [$documentTree, $new, $frmReadOutText, $form->returnForm()],
10926
            'subtab'
10927
        );
10928
10929
        return $tabs;
10930
    }
10931
10932
    /**
10933
     * Creates a list with all the exercises (quiz) in it.
10934
     *
10935
     * @return string
10936
     */
10937
    public function get_exercises()
10938
    {
10939
        $course_id = api_get_course_int_id();
10940
        $session_id = api_get_session_id();
10941
        $userInfo = api_get_user_info();
10942
10943
        // New for hotpotatoes.
10944
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
10945
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
10946
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
10947
        $condition_session = api_get_session_condition($session_id, true, true);
10948
        $setting = api_get_configuration_value('show_invisible_exercise_in_lp_list');
10949
10950
        $activeCondition = ' active <> -1 ';
10951
        if ($setting) {
10952
            $activeCondition = ' active = 1 ';
10953
        }
10954
10955
        $categoryCondition = '';
10956
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
10957
        if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
10958
            $categoryCondition = " AND exercise_category_id = $categoryId ";
10959
        }
10960
10961
        $keywordCondition = '';
10962
        $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
10963
10964
        if (!empty($keyword)) {
10965
            $keyword = Database::escape_string($keyword);
10966
            $keywordCondition = " AND title LIKE '%$keyword%' ";
10967
        }
10968
10969
        $sql_quiz = "SELECT * FROM $tbl_quiz
10970
                     WHERE
10971
                            c_id = $course_id AND
10972
                            $activeCondition
10973
                            $condition_session
10974
                            $categoryCondition
10975
                            $keywordCondition
10976
                     ORDER BY title ASC";
10977
10978
        $sql_hot = "SELECT * FROM $tbl_doc
10979
                    WHERE
10980
                        c_id = $course_id AND
10981
                        path LIKE '".$uploadPath."/%/%htm%'
10982
                        $condition_session
10983
                     ORDER BY id ASC";
10984
10985
        $res_quiz = Database::query($sql_quiz);
10986
        $res_hot = Database::query($sql_hot);
10987
10988
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
10989
10990
        // Create a search-box
10991
        $form = new FormValidator('search_simple', 'get', $currentUrl);
10992
        $form->addHidden('action', 'add_item');
10993
        $form->addHidden('type', 'step');
10994
        $form->addHidden('lp_id', $this->lp_id);
10995
        $form->addHidden('lp_build_selected', '2');
10996
10997
        $form->addCourseHiddenParams();
10998
        $form->addText(
10999
            'keyword',
11000
            get_lang('Search'),
11001
            false,
11002
            [
11003
                'aria-label' => get_lang('Search'),
11004
            ]
11005
        );
11006
11007
        if (api_get_configuration_value('allow_exercise_categories')) {
11008
            $manager = new ExerciseCategoryManager();
11009
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
11010
            if (!empty($options)) {
11011
                $form->addSelect(
11012
                    'category_id',
11013
                    get_lang('Category'),
11014
                    $options,
11015
                    ['placeholder' => get_lang('SelectAnOption')]
11016
                );
11017
            }
11018
        }
11019
11020
        $form->addButtonSearch(get_lang('Search'));
11021
        $return = $form->returnForm();
11022
11023
        $return .= '<ul class="lp_resource">';
11024
11025
        $return .= '<li class="lp_resource_element">';
11026
        $return .= Display::return_icon('new_exercice.png');
11027
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
11028
            get_lang('NewExercise').'</a>';
11029
        $return .= '</li>';
11030
11031
        $previewIcon = Display::return_icon(
11032
            'preview_view.png',
11033
            get_lang('Preview')
11034
        );
11035
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
11036
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
11037
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/showinframes.php?'.api_get_cidreq();
11038
11039
        // Display hotpotatoes
11040
        while ($row_hot = Database::fetch_array($res_hot)) {
11041
            $link = Display::url(
11042
                $previewIcon,
11043
                $exerciseUrl.'&file='.$row_hot['path'],
11044
                ['target' => '_blank']
11045
            );
11046
            $return .= '<li class="lp_resource_element" data_id="'.$row_hot['id'].'" data_type="hotpotatoes" title="'.$row_hot['title'].'" >';
11047
            $return .= '<a class="moved" href="#">';
11048
            $return .= Display::return_icon(
11049
                'move_everywhere.png',
11050
                get_lang('Move'),
11051
                [],
11052
                ICON_SIZE_TINY
11053
            );
11054
            $return .= '</a> ';
11055
            $return .= Display::return_icon('hotpotatoes_s.png');
11056
            $return .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_HOTPOTATOES.'&file='.$row_hot['id'].'&lp_id='.$this->lp_id.'">'.
11057
                ((!empty($row_hot['comment'])) ? $row_hot['comment'] : Security::remove_XSS($row_hot['title'])).$link.'</a>';
11058
            $return .= '</li>';
11059
        }
11060
11061
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
11062
        while ($row_quiz = Database::fetch_array($res_quiz)) {
11063
            $title = strip_tags(
11064
                api_html_entity_decode($row_quiz['title'])
11065
            );
11066
11067
            $visibility = api_get_item_visibility(
11068
                ['real_id' => $course_id],
11069
                TOOL_QUIZ,
11070
                $row_quiz['iid'],
11071
                $session_id
11072
            );
11073
11074
            $link = Display::url(
11075
                $previewIcon,
11076
                $exerciseUrl.'&exerciseId='.$row_quiz['iid'],
11077
                ['target' => '_blank']
11078
            );
11079
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['iid'].'" data_type="quiz" title="'.$title.'" >';
11080
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
11081
            $return .= $quizIcon;
11082
            $sessionStar = api_get_session_image(
11083
                $row_quiz['session_id'],
11084
                $userInfo['status']
11085
            );
11086
            $return .= Display::url(
11087
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
11088
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['iid'].'&lp_id='.$this->lp_id,
11089
                [
11090
                    'class' => $visibility == 0 ? 'moved text-muted' : 'moved',
11091
                ]
11092
            );
11093
            $return .= '</li>';
11094
        }
11095
11096
        $return .= '</ul>';
11097
11098
        return $return;
11099
    }
11100
11101
    /**
11102
     * Creates a list with all the links in it.
11103
     *
11104
     * @return string
11105
     */
11106
    public function get_links()
11107
    {
11108
        $selfUrl = api_get_self();
11109
        $courseIdReq = api_get_cidreq();
11110
        $course = api_get_course_info();
11111
        $userInfo = api_get_user_info();
11112
11113
        $course_id = $course['real_id'];
11114
        $tbl_link = Database::get_course_table(TABLE_LINK);
11115
        $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
11116
        $moveEverywhereIcon = Display::return_icon(
11117
            'move_everywhere.png',
11118
            get_lang('Move'),
11119
            [],
11120
            ICON_SIZE_TINY
11121
        );
11122
11123
        $session_id = api_get_session_id();
11124
        $condition_session = api_get_session_condition(
11125
            $session_id,
11126
            true,
11127
            true,
11128
            'link.session_id'
11129
        );
11130
11131
        $sql = "SELECT
11132
                    link.id as link_id,
11133
                    link.title as link_title,
11134
                    link.session_id as link_session_id,
11135
                    link.category_id as category_id,
11136
                    link_category.category_title as category_title
11137
                FROM $tbl_link as link
11138
                LEFT JOIN $linkCategoryTable as link_category
11139
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
11140
                WHERE link.c_id = $course_id $condition_session
11141
                ORDER BY link_category.category_title ASC, link.title ASC";
11142
        $result = Database::query($sql);
11143
        $categorizedLinks = [];
11144
        $categories = [];
11145
11146
        while ($link = Database::fetch_array($result)) {
11147
            if (!$link['category_id']) {
11148
                $link['category_title'] = get_lang('Uncategorized');
11149
            }
11150
            $categories[$link['category_id']] = $link['category_title'];
11151
            $categorizedLinks[$link['category_id']][$link['link_id']] = $link;
11152
        }
11153
11154
        $linksHtmlCode =
11155
            '<script>
11156
            function toggle_tool(tool, id) {
11157
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
11158
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
11159
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
11160
                } else {
11161
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
11162
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
11163
                }
11164
            }
11165
        </script>
11166
11167
        <ul class="lp_resource">
11168
            <li class="lp_resource_element">
11169
                '.Display::return_icon('linksnew.gif').'
11170
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('LinkAdd').'">'.
11171
                get_lang('LinkAdd').'
11172
                </a>
11173
            </li>';
11174
11175
        foreach ($categorizedLinks as $categoryId => $links) {
11176
            $linkNodes = null;
11177
            foreach ($links as $key => $linkInfo) {
11178
                $title = $linkInfo['link_title'];
11179
                $linkSessionId = $linkInfo['link_session_id'];
11180
11181
                $link = Display::url(
11182
                    Display::return_icon('preview_view.png', get_lang('Preview')),
11183
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
11184
                    ['target' => '_blank']
11185
                );
11186
11187
                if (api_get_item_visibility($course, TOOL_LINK, $key, $session_id) != 2) {
11188
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
11189
                    $linkNodes .=
11190
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
11191
                        <a class="moved" href="#">'.
11192
                            $moveEverywhereIcon.
11193
                        '</a>
11194
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
11195
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
11196
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
11197
                        Security::remove_XSS($title).$sessionStar.$link.
11198
                        '</a>
11199
                    </li>';
11200
                }
11201
            }
11202
            $linksHtmlCode .=
11203
                '<li>
11204
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
11205
                    <img src="'.Display::returnIconPath('add.gif').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
11206
                    align="absbottom" />
11207
                </a>
11208
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
11209
            </li>
11210
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
11211
        }
11212
        $linksHtmlCode .= '</ul>';
11213
11214
        return $linksHtmlCode;
11215
    }
11216
11217
    /**
11218
     * Creates a list with all the surveys in it.
11219
     *
11220
     * @return string
11221
     */
11222
    public function getSurveys()
11223
    {
11224
        $return = '<ul class="lp_resource">';
11225
        // First add link
11226
        $return .= '<li class="lp_resource_element">';
11227
        $return .= Display::return_icon('new_survey.png', get_lang('CreateNewSurvey'), '', ICON_SIZE_MEDIUM);
11228
        $return .= Display::url(
11229
            get_lang('CreateANewSurvey'),
11230
            api_get_path(WEB_CODE_PATH).'survey/create_new_survey.php?'.api_get_cidreq().'&'.http_build_query([
11231
                'action' => 'add',
11232
                'lp_id' => $this->lp_id,
11233
            ]),
11234
            ['title' => get_lang('CreateNewSurvey')]
11235
        );
11236
        $return .= '</li>';
11237
11238
        $surveys = SurveyManager::get_surveys(api_get_course_id(), api_get_session_id());
11239
11240
        foreach ($surveys as $survey) {
11241
            if (!empty($survey['survey_id'])) {
11242
                $surveyTitle = strip_tags($survey['title']);
11243
                $return .= '<li class="lp_resource_element" data_id="'.$survey['survey_id'].'" data_type="'.TOOL_SURVEY.'" title="'.$surveyTitle.'" >';
11244
                $return .= '<a class="moved" href="#">';
11245
                $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
11246
                $return .= ' </a>';
11247
                $return .= Display::return_icon('survey.png', '', [], ICON_SIZE_TINY);
11248
                $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>';
11249
                $return .= '</li>';
11250
            }
11251
        }
11252
11253
        $return .= '</ul>';
11254
11255
        return $return;
11256
    }
11257
11258
    /**
11259
     * Creates a list with all the student publications in it.
11260
     *
11261
     * @return string
11262
     */
11263
    public function get_student_publications()
11264
    {
11265
        $return = '<ul class="lp_resource">';
11266
        $return .= '<li class="lp_resource_element">';
11267
        $return .= Display::return_icon('works_new.gif');
11268
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
11269
            get_lang('AddAssignmentPage').'</a>';
11270
        $return .= '</li>';
11271
11272
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
11273
        $works = getWorkListTeacher(0, 100, null, null, null);
11274
        if (!empty($works)) {
11275
            foreach ($works as $work) {
11276
                $link = Display::url(
11277
                    Display::return_icon('preview_view.png', get_lang('Preview')),
11278
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
11279
                    ['target' => '_blank']
11280
                );
11281
11282
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
11283
                $return .= '<a class="moved" href="#">';
11284
                $return .= Display::return_icon(
11285
                    'move_everywhere.png',
11286
                    get_lang('Move'),
11287
                    [],
11288
                    ICON_SIZE_TINY
11289
                );
11290
                $return .= '</a> ';
11291
11292
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
11293
                $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.'">'.
11294
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
11295
                </a>';
11296
11297
                $return .= '</li>';
11298
            }
11299
        }
11300
11301
        $return .= '</ul>';
11302
11303
        return $return;
11304
    }
11305
11306
    /**
11307
     * Creates a list with all the forums in it.
11308
     *
11309
     * @return string
11310
     */
11311
    public function get_forums()
11312
    {
11313
        require_once '../forum/forumfunction.inc.php';
11314
11315
        $forumCategories = get_forum_categories();
11316
        $forumsInNoCategory = get_forums_in_category(0);
11317
        if (!empty($forumsInNoCategory)) {
11318
            $forumCategories = array_merge(
11319
                $forumCategories,
11320
                [
11321
                    [
11322
                        'cat_id' => 0,
11323
                        'session_id' => 0,
11324
                        'visibility' => 1,
11325
                        'cat_comment' => null,
11326
                    ],
11327
                ]
11328
            );
11329
        }
11330
11331
        $forumList = get_forums();
11332
        $a_forums = [];
11333
        foreach ($forumCategories as $forumCategory) {
11334
            // The forums in this category.
11335
            $forumsInCategory = get_forums_in_category($forumCategory['cat_id']);
11336
            if (!empty($forumsInCategory)) {
11337
                foreach ($forumList as $forum) {
11338
                    if (isset($forum['forum_category']) &&
11339
                        $forum['forum_category'] == $forumCategory['cat_id']
11340
                    ) {
11341
                        $a_forums[] = $forum;
11342
                    }
11343
                }
11344
            }
11345
        }
11346
11347
        $return = '<ul class="lp_resource">';
11348
11349
        // First add link
11350
        $return .= '<li class="lp_resource_element">';
11351
        $return .= Display::return_icon('new_forum.png');
11352
        $return .= Display::url(
11353
            get_lang('CreateANewForum'),
11354
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
11355
                'action' => 'add',
11356
                'content' => 'forum',
11357
                'lp_id' => $this->lp_id,
11358
            ]),
11359
            ['title' => get_lang('CreateANewForum')]
11360
        );
11361
        $return .= '</li>';
11362
11363
        $return .= '<script>
11364
            function toggle_forum(forum_id) {
11365
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
11366
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
11367
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
11368
                } else {
11369
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
11370
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
11371
                }
11372
            }
11373
        </script>';
11374
11375
        foreach ($a_forums as $forum) {
11376
            if (!empty($forum['forum_id'])) {
11377
                $link = Display::url(
11378
                    Display::return_icon('preview_view.png', get_lang('Preview')),
11379
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forum['forum_id'],
11380
                    ['target' => '_blank']
11381
                );
11382
11383
                $return .= '<li class="lp_resource_element" data_id="'.$forum['forum_id'].'" data_type="'.TOOL_FORUM.'" title="'.$forum['forum_title'].'" >';
11384
                $return .= '<a class="moved" href="#">';
11385
                $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
11386
                $return .= ' </a>';
11387
                $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
11388
                $return .= '<a onclick="javascript:toggle_forum('.$forum['forum_id'].');" style="cursor:hand; vertical-align:middle">
11389
                                <img src="'.Display::returnIconPath('add.gif').'" id="forum_'.$forum['forum_id'].'_opener" align="absbottom" />
11390
                            </a>
11391
                            <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">'.
11392
                    Security::remove_XSS($forum['forum_title']).' '.$link.'</a>';
11393
11394
                $return .= '</li>';
11395
11396
                $return .= '<div style="display:none" id="forum_'.$forum['forum_id'].'_content">';
11397
                $a_threads = get_threads($forum['forum_id']);
11398
                if (is_array($a_threads)) {
11399
                    foreach ($a_threads as $thread) {
11400
                        $link = Display::url(
11401
                            Display::return_icon('preview_view.png', get_lang('Preview')),
11402
                            api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forum['forum_id'].'&thread='.$thread['thread_id'],
11403
                            ['target' => '_blank']
11404
                        );
11405
11406
                        $return .= '<li class="lp_resource_element" data_id="'.$thread['thread_id'].'" data_type="'.TOOL_THREAD.'" title="'.$thread['thread_title'].'" >';
11407
                        $return .= '&nbsp;<a class="moved" href="#">';
11408
                        $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
11409
                        $return .= ' </a>';
11410
                        $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
11411
                        $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.'">'.
11412
                            Security::remove_XSS($thread['thread_title']).' '.$link.'</a>';
11413
                        $return .= '</li>';
11414
                    }
11415
                }
11416
                $return .= '</div>';
11417
            }
11418
        }
11419
        $return .= '</ul>';
11420
11421
        return $return;
11422
    }
11423
11424
    /**
11425
     * // TODO: The output encoding should be equal to the system encoding.
11426
     *
11427
     * Exports the learning path as a SCORM package. This is the main function that
11428
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
11429
     * whole thing and returns the zip.
11430
     *
11431
     * This method needs to be called in PHP5, as it will fail with non-adequate
11432
     * XML package (like the ones for PHP4), and it is *not* a static method, so
11433
     * you need to call it on a learnpath object.
11434
     *
11435
     * @TODO The method might be redefined later on in the scorm class itself to avoid
11436
     * creating a SCORM structure if there is one already. However, if the initial SCORM
11437
     * path has been modified, it should use the generic method here below.
11438
     *
11439
     * @return string Returns the zip package string, or null if error
11440
     */
11441
    public function scormExport()
11442
    {
11443
        api_set_more_memory_and_time_limits();
11444
11445
        $_course = api_get_course_info();
11446
        $course_id = $_course['real_id'];
11447
        // Create the zip handler (this will remain available throughout the method).
11448
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
11449
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
11450
        $temp_dir_short = uniqid('scorm_export', true);
11451
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
11452
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
11453
        $zip_folder = new PclZip($temp_zip_file);
11454
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
11455
        $root_path = $main_path = api_get_path(SYS_PATH);
11456
        $files_cleanup = [];
11457
11458
        // Place to temporarily stash the zip file.
11459
        // create the temp dir if it doesn't exist
11460
        // or do a cleanup before creating the zip file.
11461
        if (!is_dir($temp_zip_dir)) {
11462
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
11463
        } else {
11464
            // Cleanup: Check the temp dir for old files and delete them.
11465
            $handle = opendir($temp_zip_dir);
11466
            while (false !== ($file = readdir($handle))) {
11467
                if ($file != '.' && $file != '..') {
11468
                    unlink("$temp_zip_dir/$file");
11469
                }
11470
            }
11471
            closedir($handle);
11472
        }
11473
        $zip_files = $zip_files_abs = $zip_files_dist = [];
11474
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
11475
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
11476
        ) {
11477
            // Remove the possible . at the end of the path.
11478
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
11479
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
11480
            mkdir(
11481
                $dest_path_to_scorm_folder,
11482
                api_get_permissions_for_new_directories(),
11483
                true
11484
            );
11485
            copyr(
11486
                $current_course_path.'/scorm/'.$this->path,
11487
                $dest_path_to_scorm_folder,
11488
                ['imsmanifest'],
11489
                $zip_files
11490
            );
11491
        }
11492
11493
        // Build a dummy imsmanifest structure.
11494
        // Do not add to the zip yet (we still need it).
11495
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
11496
        // Aggregation Model official document, section "2.3 Content Packaging".
11497
        // We are going to build a UTF-8 encoded manifest.
11498
        // Later we will recode it to the desired (and supported) encoding.
11499
        $xmldoc = new DOMDocument('1.0');
11500
        $root = $xmldoc->createElement('manifest');
11501
        $root->setAttribute('identifier', 'SingleCourseManifest');
11502
        $root->setAttribute('version', '1.1');
11503
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
11504
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
11505
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
11506
        $root->setAttribute(
11507
            'xsi:schemaLocation',
11508
            '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'
11509
        );
11510
        // Build mandatory sub-root container elements.
11511
        $metadata = $xmldoc->createElement('metadata');
11512
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
11513
        $metadata->appendChild($md_schema);
11514
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
11515
        $metadata->appendChild($md_schemaversion);
11516
        $root->appendChild($metadata);
11517
11518
        $organizations = $xmldoc->createElement('organizations');
11519
        $resources = $xmldoc->createElement('resources');
11520
11521
        // Build the only organization we will use in building our learnpaths.
11522
        $organizations->setAttribute('default', 'chamilo_scorm_export');
11523
        $organization = $xmldoc->createElement('organization');
11524
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
11525
        // To set the title of the SCORM entity (=organization), we take the name given
11526
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
11527
        // learning path charset) as it is the encoding that defines how it is stored
11528
        // in the database. Then we convert it to HTML entities again as the "&" character
11529
        // alone is not authorized in XML (must be &amp;).
11530
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
11531
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
11532
        $organization->appendChild($org_title);
11533
        $folder_name = 'document';
11534
11535
        // Removes the learning_path/scorm_folder path when exporting see #4841
11536
        $path_to_remove = '';
11537
        $path_to_replace = '';
11538
        $result = $this->generate_lp_folder($_course);
11539
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
11540
            $path_to_remove = 'document'.$result['dir'];
11541
            $path_to_replace = $folder_name.'/';
11542
        }
11543
11544
        // Fixes chamilo scorm exports
11545
        if ($this->ref === 'chamilo_scorm_export') {
11546
            $path_to_remove = 'scorm/'.$this->path.'/document/';
11547
        }
11548
11549
        // For each element, add it to the imsmanifest structure, then add it to the zip.
11550
        $link_updates = [];
11551
        $links_to_create = [];
11552
        foreach ($this->ordered_items as $index => $itemId) {
11553
            /** @var learnpathItem $item */
11554
            $item = $this->items[$itemId];
11555
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
11556
                // Get included documents from this item.
11557
                if ($item->type === 'sco') {
11558
                    $inc_docs = $item->get_resources_from_source(
11559
                        null,
11560
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
11561
                    );
11562
                } else {
11563
                    $inc_docs = $item->get_resources_from_source();
11564
                }
11565
11566
                // Give a child element <item> to the <organization> element.
11567
                $my_item_id = $item->get_id();
11568
                $my_item = $xmldoc->createElement('item');
11569
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
11570
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
11571
                $my_item->setAttribute('isvisible', 'true');
11572
                // Give a child element <title> to the <item> element.
11573
                $my_title = $xmldoc->createElement(
11574
                    'title',
11575
                    htmlspecialchars(
11576
                        api_utf8_encode($item->get_title()),
11577
                        ENT_QUOTES,
11578
                        'UTF-8'
11579
                    )
11580
                );
11581
                $my_item->appendChild($my_title);
11582
                // Give a child element <adlcp:prerequisites> to the <item> element.
11583
                $my_prereqs = $xmldoc->createElement(
11584
                    'adlcp:prerequisites',
11585
                    $this->get_scorm_prereq_string($my_item_id)
11586
                );
11587
                $my_prereqs->setAttribute('type', 'aicc_script');
11588
                $my_item->appendChild($my_prereqs);
11589
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
11590
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
11591
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
11592
                //$xmldoc->createElement('adlcp:timelimitaction','');
11593
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
11594
                //$xmldoc->createElement('adlcp:datafromlms','');
11595
                // Give a child element <adlcp:masteryscore> to the <item> element.
11596
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11597
                $my_item->appendChild($my_masteryscore);
11598
11599
                // Attach this item to the organization element or hits parent if there is one.
11600
                if (!empty($item->parent) && $item->parent != 0) {
11601
                    $children = $organization->childNodes;
11602
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11603
                    if (is_object($possible_parent)) {
11604
                        $possible_parent->appendChild($my_item);
11605
                    } else {
11606
                        if ($this->debug > 0) {
11607
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
11608
                        }
11609
                    }
11610
                } else {
11611
                    if ($this->debug > 0) {
11612
                        error_log('No parent');
11613
                    }
11614
                    $organization->appendChild($my_item);
11615
                }
11616
11617
                // Get the path of the file(s) from the course directory root.
11618
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11619
                $my_xml_file_path = $my_file_path;
11620
                if (!empty($path_to_remove)) {
11621
                    // From docs
11622
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
11623
11624
                    // From quiz
11625
                    if ($this->ref === 'chamilo_scorm_export') {
11626
                        $path_to_remove = 'scorm/'.$this->path.'/';
11627
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
11628
                    }
11629
                }
11630
11631
                $my_sub_dir = dirname($my_file_path);
11632
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11633
                $my_xml_sub_dir = $my_sub_dir;
11634
                // Give a <resource> child to the <resources> element
11635
                $my_resource = $xmldoc->createElement('resource');
11636
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11637
                $my_resource->setAttribute('type', 'webcontent');
11638
                $my_resource->setAttribute('href', $my_xml_file_path);
11639
                // adlcp:scormtype can be either 'sco' or 'asset'.
11640
                if ($item->type === 'sco') {
11641
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
11642
                } else {
11643
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
11644
                }
11645
                // xml:base is the base directory to find the files declared in this resource.
11646
                $my_resource->setAttribute('xml:base', '');
11647
                // Give a <file> child to the <resource> element.
11648
                $my_file = $xmldoc->createElement('file');
11649
                $my_file->setAttribute('href', $my_xml_file_path);
11650
                $my_resource->appendChild($my_file);
11651
11652
                // Dependency to other files - not yet supported.
11653
                $i = 1;
11654
                if ($inc_docs) {
11655
                    foreach ($inc_docs as $doc_info) {
11656
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
11657
                            continue;
11658
                        }
11659
                        $my_dep = $xmldoc->createElement('resource');
11660
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11661
                        $my_dep->setAttribute('identifier', $res_id);
11662
                        $my_dep->setAttribute('type', 'webcontent');
11663
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
11664
                        $my_dep_file = $xmldoc->createElement('file');
11665
                        // Check type of URL.
11666
                        if ($doc_info[1] == 'remote') {
11667
                            // Remote file. Save url as is.
11668
                            $my_dep_file->setAttribute('href', $doc_info[0]);
11669
                            $my_dep->setAttribute('xml:base', '');
11670
                        } elseif ($doc_info[1] === 'local') {
11671
                            switch ($doc_info[2]) {
11672
                                case 'url':
11673
                                    // Local URL - save path as url for now, don't zip file.
11674
                                    $abs_path = api_get_path(SYS_PATH).
11675
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11676
                                    $current_dir = dirname($abs_path);
11677
                                    $current_dir = str_replace('\\', '/', $current_dir);
11678
                                    $file_path = realpath($abs_path);
11679
                                    $file_path = str_replace('\\', '/', $file_path);
11680
                                    $my_dep_file->setAttribute('href', $file_path);
11681
                                    $my_dep->setAttribute('xml:base', '');
11682
                                    if (strstr($file_path, $main_path) !== false) {
11683
                                        // The calculated real path is really inside Chamilo's root path.
11684
                                        // Reduce file path to what's under the DocumentRoot.
11685
                                        $replace = $file_path;
11686
                                        $file_path = substr($file_path, strlen($root_path) - 1);
11687
                                        $destinationFile = $file_path;
11688
11689
                                        if (strstr($file_path, 'upload/users') !== false) {
11690
                                            $pos = strpos($file_path, 'my_files/');
11691
                                            if ($pos !== false) {
11692
                                                $onlyDirectory = str_replace(
11693
                                                    'upload/users/',
11694
                                                    '',
11695
                                                    substr($file_path, $pos, strlen($file_path))
11696
                                                );
11697
                                            }
11698
                                            $replace = './'.$onlyDirectory;
11699
                                            $destinationFile = $replace;
11700
                                        }
11701
11702
                                        if (strpos($file_path, '/web') === 0) {
11703
                                            $replace = str_replace('/web', 'web', $file_path);
11704
                                        }
11705
11706
                                        $zip_files_abs[] = $file_path;
11707
                                        $link_updates[$my_file_path][] = [
11708
                                            'orig' => $doc_info[0],
11709
                                            'dest' => $destinationFile,
11710
                                            'replace' => $replace,
11711
                                        ];
11712
                                        $my_dep_file->setAttribute('href', $file_path);
11713
                                        $my_dep->setAttribute('xml:base', '');
11714
                                    } elseif (empty($file_path)) {
11715
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11716
                                        $file_path = str_replace('//', '/', $file_path);
11717
                                        if (file_exists($file_path)) {
11718
                                            // We get the relative path.
11719
                                            $file_path = substr($file_path, strlen($current_dir));
11720
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
11721
                                            $link_updates[$my_file_path][] = [
11722
                                                'orig' => $doc_info[0],
11723
                                                'dest' => $file_path,
11724
                                            ];
11725
                                            $my_dep_file->setAttribute('href', $file_path);
11726
                                            $my_dep->setAttribute('xml:base', '');
11727
                                        }
11728
                                    }
11729
                                    break;
11730
                                case 'abs':
11731
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11732
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11733
                                    $my_dep->setAttribute('xml:base', '');
11734
11735
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
11736
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
11737
                                    $abs_img_path_without_subdir = $doc_info[0];
11738
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
11739
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
11740
                                    if ($pos === 0) {
11741
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
11742
                                    }
11743
11744
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
11745
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
11746
11747
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
11748
                                    $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
11749
                                    // Check if the current document is in that path.
11750
                                    if (strstr($file_path, $cur_path) !== false) {
11751
                                        $destinationFile = substr($file_path, strlen($cur_path));
11752
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
11753
11754
                                        $fileToTest = $cur_path.$my_file_path;
11755
                                        if (!empty($path_to_remove)) {
11756
                                            $fileToTest = str_replace(
11757
                                                $path_to_remove.'/',
11758
                                                $path_to_replace,
11759
                                                $cur_path.$my_file_path
11760
                                            );
11761
                                        }
11762
11763
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
11764
11765
                                        // Put the current document in the zip (this array is the array
11766
                                        // that will manage documents already in the course folder - relative).
11767
                                        $zip_files[] = $filePathNoCoursePart;
11768
                                        // Update the links to the current document in the
11769
                                        // containing document (make them relative).
11770
                                        $link_updates[$my_file_path][] = [
11771
                                            'orig' => $doc_info[0],
11772
                                            'dest' => $destinationFile,
11773
                                            'replace' => $relative_path,
11774
                                        ];
11775
11776
                                        $my_dep_file->setAttribute('href', $file_path);
11777
                                        $my_dep->setAttribute('xml:base', '');
11778
                                    } elseif (strstr($file_path, $main_path) !== false) {
11779
                                        // The calculated real path is really inside Chamilo's root path.
11780
                                        // Reduce file path to what's under the DocumentRoot.
11781
                                        $file_path = substr($file_path, strlen($root_path));
11782
                                        $zip_files_abs[] = $file_path;
11783
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11784
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11785
                                        $my_dep->setAttribute('xml:base', '');
11786
                                    } elseif (empty($file_path)) {
11787
                                        // Probably this is an image inside "/main" directory
11788
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
11789
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11790
11791
                                        if (file_exists($file_path)) {
11792
                                            $pos = strpos($file_path, 'main/default_course_document/');
11793
                                            if ($pos !== false) {
11794
                                                // We get the relative path.
11795
                                                $onlyDirectory = str_replace(
11796
                                                    'main/default_course_document/',
11797
                                                    '',
11798
                                                    substr($file_path, $pos, strlen($file_path))
11799
                                                );
11800
11801
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
11802
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
11803
                                                $zip_files_abs[] = $fileAbs;
11804
                                                $link_updates[$my_file_path][] = [
11805
                                                    'orig' => $doc_info[0],
11806
                                                    'dest' => $destinationFile,
11807
                                                ];
11808
                                                $my_dep_file->setAttribute('href', 'document/'.$destinationFile);
11809
                                                $my_dep->setAttribute('xml:base', '');
11810
                                            }
11811
                                        }
11812
                                    }
11813
                                    break;
11814
                                case 'rel':
11815
                                    // Path relative to the current document.
11816
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
11817
                                    if (substr($doc_info[0], 0, 2) === '..') {
11818
                                        // Relative path going up.
11819
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11820
                                        $current_dir = str_replace('\\', '/', $current_dir);
11821
                                        $file_path = realpath($current_dir.$doc_info[0]);
11822
                                        $file_path = str_replace('\\', '/', $file_path);
11823
                                        if (strstr($file_path, $main_path) !== false) {
11824
                                            // The calculated real path is really inside Chamilo's root path.
11825
                                            // Reduce file path to what's under the DocumentRoot.
11826
                                            $file_path = substr($file_path, strlen($root_path));
11827
                                            $zip_files_abs[] = $file_path;
11828
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11829
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11830
                                            $my_dep->setAttribute('xml:base', '');
11831
                                        }
11832
                                    } else {
11833
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11834
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
11835
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11836
                                    }
11837
                                    break;
11838
                                default:
11839
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11840
                                    $my_dep->setAttribute('xml:base', '');
11841
                                    break;
11842
                            }
11843
                        }
11844
                        $my_dep->appendChild($my_dep_file);
11845
                        $resources->appendChild($my_dep);
11846
                        $dependency = $xmldoc->createElement('dependency');
11847
                        $dependency->setAttribute('identifierref', $res_id);
11848
                        $my_resource->appendChild($dependency);
11849
                        $i++;
11850
                    }
11851
                }
11852
                $resources->appendChild($my_resource);
11853
                $zip_files[] = $my_file_path;
11854
            } else {
11855
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
11856
                switch ($item->type) {
11857
                    case TOOL_LINK:
11858
                        $my_item = $xmldoc->createElement('item');
11859
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11860
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11861
                        $my_item->setAttribute('isvisible', 'true');
11862
                        // Give a child element <title> to the <item> element.
11863
                        $my_title = $xmldoc->createElement(
11864
                            'title',
11865
                            htmlspecialchars(
11866
                                api_utf8_encode($item->get_title()),
11867
                                ENT_QUOTES,
11868
                                'UTF-8'
11869
                            )
11870
                        );
11871
                        $my_item->appendChild($my_title);
11872
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11873
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11874
                        $my_prereqs->setAttribute('type', 'aicc_script');
11875
                        $my_item->appendChild($my_prereqs);
11876
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
11877
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
11878
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
11879
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
11880
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
11881
                        //$xmldoc->createElement('adlcp:datafromlms', '');
11882
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11883
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11884
                        $my_item->appendChild($my_masteryscore);
11885
11886
                        // Attach this item to the organization element or its parent if there is one.
11887
                        if (!empty($item->parent) && $item->parent != 0) {
11888
                            $children = $organization->childNodes;
11889
                            for ($i = 0; $i < $children->length; $i++) {
11890
                                $item_temp = $children->item($i);
11891
                                if ($item_temp->nodeName == 'item') {
11892
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
11893
                                        $item_temp->appendChild($my_item);
11894
                                    }
11895
                                }
11896
                            }
11897
                        } else {
11898
                            $organization->appendChild($my_item);
11899
                        }
11900
11901
                        $my_file_path = 'link_'.$item->get_id().'.html';
11902
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
11903
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
11904
                        $rs = Database::query($sql);
11905
                        if ($link = Database::fetch_array($rs)) {
11906
                            $url = $link['url'];
11907
                            $title = stripslashes($link['title']);
11908
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
11909
                            $my_xml_file_path = $my_file_path;
11910
                            $my_sub_dir = dirname($my_file_path);
11911
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11912
                            $my_xml_sub_dir = $my_sub_dir;
11913
                            // Give a <resource> child to the <resources> element.
11914
                            $my_resource = $xmldoc->createElement('resource');
11915
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11916
                            $my_resource->setAttribute('type', 'webcontent');
11917
                            $my_resource->setAttribute('href', $my_xml_file_path);
11918
                            // adlcp:scormtype can be either 'sco' or 'asset'.
11919
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
11920
                            // xml:base is the base directory to find the files declared in this resource.
11921
                            $my_resource->setAttribute('xml:base', '');
11922
                            // give a <file> child to the <resource> element.
11923
                            $my_file = $xmldoc->createElement('file');
11924
                            $my_file->setAttribute('href', $my_xml_file_path);
11925
                            $my_resource->appendChild($my_file);
11926
                            $resources->appendChild($my_resource);
11927
                        }
11928
                        break;
11929
                    case TOOL_QUIZ:
11930
                        $exe_id = $item->path;
11931
                        // Should be using ref when everything will be cleaned up in this regard.
11932
                        $exe = new Exercise();
11933
                        $exe->read($exe_id);
11934
                        $my_item = $xmldoc->createElement('item');
11935
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11936
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11937
                        $my_item->setAttribute('isvisible', 'true');
11938
                        // Give a child element <title> to the <item> element.
11939
                        $my_title = $xmldoc->createElement(
11940
                            'title',
11941
                            htmlspecialchars(
11942
                                api_utf8_encode($item->get_title()),
11943
                                ENT_QUOTES,
11944
                                'UTF-8'
11945
                            )
11946
                        );
11947
                        $my_item->appendChild($my_title);
11948
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
11949
                        $my_item->appendChild($my_max_score);
11950
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11951
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11952
                        $my_prereqs->setAttribute('type', 'aicc_script');
11953
                        $my_item->appendChild($my_prereqs);
11954
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11955
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11956
                        $my_item->appendChild($my_masteryscore);
11957
11958
                        // Attach this item to the organization element or hits parent if there is one.
11959
                        if (!empty($item->parent) && $item->parent != 0) {
11960
                            $children = $organization->childNodes;
11961
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11962
                            if ($possible_parent) {
11963
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
11964
                                    $possible_parent->appendChild($my_item);
11965
                                }
11966
                            }
11967
                        } else {
11968
                            $organization->appendChild($my_item);
11969
                        }
11970
11971
                        // Get the path of the file(s) from the course directory root
11972
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11973
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
11974
                        // Write the contents of the exported exercise into a (big) html file
11975
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
11976
                        $scormExercise = new ScormExercise($exe, true);
11977
                        $contents = $scormExercise->export();
11978
11979
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
11980
                        $res = file_put_contents($tmp_file_path, $contents);
11981
                        if ($res === false) {
11982
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
11983
                        }
11984
                        $files_cleanup[] = $tmp_file_path;
11985
                        $my_xml_file_path = $my_file_path;
11986
                        $my_sub_dir = dirname($my_file_path);
11987
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11988
                        $my_xml_sub_dir = $my_sub_dir;
11989
                        // Give a <resource> child to the <resources> element.
11990
                        $my_resource = $xmldoc->createElement('resource');
11991
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11992
                        $my_resource->setAttribute('type', 'webcontent');
11993
                        $my_resource->setAttribute('href', $my_xml_file_path);
11994
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11995
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
11996
                        // xml:base is the base directory to find the files declared in this resource.
11997
                        $my_resource->setAttribute('xml:base', '');
11998
                        // Give a <file> child to the <resource> element.
11999
                        $my_file = $xmldoc->createElement('file');
12000
                        $my_file->setAttribute('href', $my_xml_file_path);
12001
                        $my_resource->appendChild($my_file);
12002
12003
                        // Get included docs.
12004
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
12005
12006
                        // Dependency to other files - not yet supported.
12007
                        $i = 1;
12008
                        foreach ($inc_docs as $doc_info) {
12009
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
12010
                                continue;
12011
                            }
12012
                            $my_dep = $xmldoc->createElement('resource');
12013
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
12014
                            $my_dep->setAttribute('identifier', $res_id);
12015
                            $my_dep->setAttribute('type', 'webcontent');
12016
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
12017
                            $my_dep_file = $xmldoc->createElement('file');
12018
                            // Check type of URL.
12019
                            if ($doc_info[1] == 'remote') {
12020
                                // Remote file. Save url as is.
12021
                                $my_dep_file->setAttribute('href', $doc_info[0]);
12022
                                $my_dep->setAttribute('xml:base', '');
12023
                            } elseif ($doc_info[1] == 'local') {
12024
                                switch ($doc_info[2]) {
12025
                                    case 'url': // Local URL - save path as url for now, don't zip file.
12026
                                        // Save file but as local file (retrieve from URL).
12027
                                        $abs_path = api_get_path(SYS_PATH).
12028
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
12029
                                        $current_dir = dirname($abs_path);
12030
                                        $current_dir = str_replace('\\', '/', $current_dir);
12031
                                        $file_path = realpath($abs_path);
12032
                                        $file_path = str_replace('\\', '/', $file_path);
12033
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
12034
                                        $my_dep->setAttribute('xml:base', '');
12035
                                        if (strstr($file_path, $main_path) !== false) {
12036
                                            // The calculated real path is really inside the chamilo root path.
12037
                                            // Reduce file path to what's under the DocumentRoot.
12038
                                            $file_path = substr($file_path, strlen($root_path));
12039
                                            $zip_files_abs[] = $file_path;
12040
                                            $link_updates[$my_file_path][] = [
12041
                                                'orig' => $doc_info[0],
12042
                                                'dest' => 'document/'.$file_path,
12043
                                            ];
12044
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
12045
                                            $my_dep->setAttribute('xml:base', '');
12046
                                        } elseif (empty($file_path)) {
12047
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
12048
                                            $file_path = str_replace('//', '/', $file_path);
12049
                                            if (file_exists($file_path)) {
12050
                                                $file_path = substr($file_path, strlen($current_dir));
12051
                                                // We get the relative path.
12052
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
12053
                                                $link_updates[$my_file_path][] = [
12054
                                                    'orig' => $doc_info[0],
12055
                                                    'dest' => 'document/'.$file_path,
12056
                                                ];
12057
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
12058
                                                $my_dep->setAttribute('xml:base', '');
12059
                                            }
12060
                                        }
12061
                                        break;
12062
                                    case 'abs':
12063
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
12064
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
12065
                                        $current_dir = str_replace('\\', '/', $current_dir);
12066
                                        $file_path = realpath($doc_info[0]);
12067
                                        $file_path = str_replace('\\', '/', $file_path);
12068
                                        $my_dep_file->setAttribute('href', $file_path);
12069
                                        $my_dep->setAttribute('xml:base', '');
12070
12071
                                        if (strstr($file_path, $main_path) !== false) {
12072
                                            // The calculated real path is really inside the chamilo root path.
12073
                                            // Reduce file path to what's under the DocumentRoot.
12074
                                            $file_path = substr($file_path, strlen($root_path));
12075
                                            $zip_files_abs[] = $file_path;
12076
                                            $link_updates[$my_file_path][] = [
12077
                                                'orig' => $doc_info[0],
12078
                                                'dest' => $file_path,
12079
                                            ];
12080
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
12081
                                            $my_dep->setAttribute('xml:base', '');
12082
                                        } elseif (empty($file_path)) {
12083
                                            $docSysPartPath = str_replace(
12084
                                                api_get_path(REL_COURSE_PATH),
12085
                                                '',
12086
                                                $doc_info[0]
12087
                                            );
12088
12089
                                            $docSysPartPathNoCourseCode = str_replace(
12090
                                                $_course['directory'].'/',
12091
                                                '',
12092
                                                $docSysPartPath
12093
                                            );
12094
12095
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
12096
                                            if (file_exists($docSysPath)) {
12097
                                                $file_path = $docSysPartPathNoCourseCode;
12098
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
12099
                                                $link_updates[$my_file_path][] = [
12100
                                                    'orig' => $doc_info[0],
12101
                                                    'dest' => $file_path,
12102
                                                ];
12103
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
12104
                                                $my_dep->setAttribute('xml:base', '');
12105
                                            }
12106
                                        }
12107
                                        break;
12108
                                    case 'rel':
12109
                                        // Path relative to the current document. Save xml:base as current document's
12110
                                        // directory and save file in zip as subdir.file_path
12111
                                        if (substr($doc_info[0], 0, 2) === '..') {
12112
                                            // Relative path going up.
12113
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
12114
                                            $current_dir = str_replace('\\', '/', $current_dir);
12115
                                            $file_path = realpath($current_dir.$doc_info[0]);
12116
                                            $file_path = str_replace('\\', '/', $file_path);
12117
                                            if (strstr($file_path, $main_path) !== false) {
12118
                                                // The calculated real path is really inside Chamilo's root path.
12119
                                                // Reduce file path to what's under the DocumentRoot.
12120
12121
                                                $file_path = substr($file_path, strlen($root_path));
12122
                                                $file_path_dest = $file_path;
12123
12124
                                                // File path is courses/CHAMILO/document/....
12125
                                                $info_file_path = explode('/', $file_path);
12126
                                                if ($info_file_path[0] == 'courses') {
12127
                                                    // Add character "/" in file path.
12128
                                                    $file_path_dest = 'document/'.$file_path;
12129
                                                }
12130
                                                $zip_files_abs[] = $file_path;
12131
12132
                                                $link_updates[$my_file_path][] = [
12133
                                                    'orig' => $doc_info[0],
12134
                                                    'dest' => $file_path_dest,
12135
                                                ];
12136
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
12137
                                                $my_dep->setAttribute('xml:base', '');
12138
                                            }
12139
                                        } else {
12140
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
12141
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
12142
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
12143
                                        }
12144
                                        break;
12145
                                    default:
12146
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
12147
                                        $my_dep->setAttribute('xml:base', '');
12148
                                        break;
12149
                                }
12150
                            }
12151
                            $my_dep->appendChild($my_dep_file);
12152
                            $resources->appendChild($my_dep);
12153
                            $dependency = $xmldoc->createElement('dependency');
12154
                            $dependency->setAttribute('identifierref', $res_id);
12155
                            $my_resource->appendChild($dependency);
12156
                            $i++;
12157
                        }
12158
                        $resources->appendChild($my_resource);
12159
                        $zip_files[] = $my_file_path;
12160
                        break;
12161
                    default:
12162
                        // Get the path of the file(s) from the course directory root
12163
                        $my_file_path = 'non_exportable.html';
12164
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
12165
                        $my_xml_file_path = $my_file_path;
12166
                        $my_sub_dir = dirname($my_file_path);
12167
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
12168
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
12169
                        $my_xml_sub_dir = $my_sub_dir;
12170
                        // Give a <resource> child to the <resources> element.
12171
                        $my_resource = $xmldoc->createElement('resource');
12172
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
12173
                        $my_resource->setAttribute('type', 'webcontent');
12174
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
12175
                        // adlcp:scormtype can be either 'sco' or 'asset'.
12176
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
12177
                        // xml:base is the base directory to find the files declared in this resource.
12178
                        $my_resource->setAttribute('xml:base', '');
12179
                        // Give a <file> child to the <resource> element.
12180
                        $my_file = $xmldoc->createElement('file');
12181
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
12182
                        $my_resource->appendChild($my_file);
12183
                        $resources->appendChild($my_resource);
12184
                        break;
12185
                }
12186
            }
12187
        }
12188
        $organizations->appendChild($organization);
12189
        $root->appendChild($organizations);
12190
        $root->appendChild($resources);
12191
        $xmldoc->appendChild($root);
12192
12193
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
12194
12195
        // then add the file to the zip, then destroy the file (this is done automatically).
12196
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
12197
        foreach ($zip_files as $file_path) {
12198
            if (empty($file_path)) {
12199
                continue;
12200
            }
12201
12202
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
12203
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
12204
12205
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
12206
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
12207
            }
12208
12209
            $this->create_path($dest_file);
12210
            @copy($filePath, $dest_file);
12211
12212
            // Check if the file needs a link update.
12213
            if (in_array($file_path, array_keys($link_updates))) {
12214
                $string = file_get_contents($dest_file);
12215
                unlink($dest_file);
12216
                foreach ($link_updates[$file_path] as $old_new) {
12217
                    // This is an ugly hack that allows .flv files to be found by the flv player that
12218
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
12219
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
12220
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
12221
                    if (substr($old_new['dest'], -3) === 'flv' &&
12222
                        substr($old_new['dest'], 0, 5) === 'main/'
12223
                    ) {
12224
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
12225
                    } elseif (substr($old_new['dest'], -3) === 'flv' &&
12226
                        substr($old_new['dest'], 0, 6) === 'video/'
12227
                    ) {
12228
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
12229
                    }
12230
12231
                    // Fix to avoid problems with default_course_document
12232
                    if (strpos('main/default_course_document', $old_new['dest']) === false) {
12233
                        $newDestination = $old_new['dest'];
12234
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
12235
                            $newDestination = $old_new['replace'];
12236
                        }
12237
                    } else {
12238
                        $newDestination = str_replace('document/', '', $old_new['dest']);
12239
                    }
12240
                    $string = str_replace($old_new['orig'], $newDestination, $string);
12241
12242
                    // Add files inside the HTMLs
12243
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
12244
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
12245
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
12246
                        copy(
12247
                            $sys_course_path.$new_path,
12248
                            $destinationFile
12249
                        );
12250
                    }
12251
                }
12252
                file_put_contents($dest_file, $string);
12253
            }
12254
12255
            if (file_exists($filePath) && $copyAll) {
12256
                $extension = $this->get_extension($filePath);
12257
                if (in_array($extension, ['html', 'html'])) {
12258
                    $containerOrigin = dirname($filePath);
12259
                    $containerDestination = dirname($dest_file);
12260
12261
                    $finder = new Finder();
12262
                    $finder->files()->in($containerOrigin)
12263
                        ->notName('*_DELETED_*')
12264
                        ->exclude('share_folder')
12265
                        ->exclude('chat_files')
12266
                        ->exclude('certificates')
12267
                    ;
12268
12269
                    if (is_dir($containerOrigin) &&
12270
                        is_dir($containerDestination)
12271
                    ) {
12272
                        $fs = new Filesystem();
12273
                        $fs->mirror(
12274
                            $containerOrigin,
12275
                            $containerDestination,
12276
                            $finder
12277
                        );
12278
                    }
12279
                }
12280
            }
12281
        }
12282
12283
        foreach ($zip_files_abs as $file_path) {
12284
            if (empty($file_path)) {
12285
                continue;
12286
            }
12287
12288
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
12289
                continue;
12290
            }
12291
12292
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
12293
            if (strstr($file_path, 'upload/users') !== false) {
12294
                $pos = strpos($file_path, 'my_files/');
12295
                if ($pos !== false) {
12296
                    $onlyDirectory = str_replace(
12297
                        'upload/users/',
12298
                        '',
12299
                        substr($file_path, $pos, strlen($file_path))
12300
                    );
12301
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
12302
                }
12303
            }
12304
12305
            if (strstr($file_path, 'default_course_document/') !== false) {
12306
                $replace = str_replace('/main', '', $file_path);
12307
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
12308
            }
12309
12310
            if (empty($dest_file)) {
12311
                continue;
12312
            }
12313
12314
            $this->create_path($dest_file);
12315
            copy($main_path.$file_path, $dest_file);
12316
            // Check if the file needs a link update.
12317
            if (in_array($file_path, array_keys($link_updates))) {
12318
                $string = file_get_contents($dest_file);
12319
                unlink($dest_file);
12320
                foreach ($link_updates[$file_path] as $old_new) {
12321
                    // This is an ugly hack that allows .flv files to be found by the flv player that
12322
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
12323
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
12324
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
12325
                    if (substr($old_new['dest'], -3) == 'flv' &&
12326
                        substr($old_new['dest'], 0, 5) == 'main/'
12327
                    ) {
12328
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
12329
                    }
12330
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
12331
                }
12332
                file_put_contents($dest_file, $string);
12333
            }
12334
        }
12335
12336
        if (is_array($links_to_create)) {
12337
            foreach ($links_to_create as $file => $link) {
12338
                $content = '<!DOCTYPE html><head>
12339
                            <meta charset="'.api_get_language_isocode().'" />
12340
                            <title>'.$link['title'].'</title>
12341
                            </head>
12342
                            <body dir="'.api_get_text_direction().'">
12343
                            <div style="text-align:center">
12344
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
12345
                            </body>
12346
                            </html>';
12347
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
12348
            }
12349
        }
12350
12351
        // Add non exportable message explanation.
12352
        $lang_not_exportable = get_lang('ThisItemIsNotExportable');
12353
        $file_content = '<!DOCTYPE html><head>
12354
                        <meta charset="'.api_get_language_isocode().'" />
12355
                        <title>'.$lang_not_exportable.'</title>
12356
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
12357
                        </head>
12358
                        <body dir="'.api_get_text_direction().'">';
12359
        $file_content .=
12360
            <<<EOD
12361
                    <style>
12362
            .error-message {
12363
                font-family: arial, verdana, helvetica, sans-serif;
12364
                border-width: 1px;
12365
                border-style: solid;
12366
                left: 50%;
12367
                margin: 10px auto;
12368
                min-height: 30px;
12369
                padding: 5px;
12370
                right: 50%;
12371
                width: 500px;
12372
                background-color: #FFD1D1;
12373
                border-color: #FF0000;
12374
                color: #000;
12375
            }
12376
        </style>
12377
    <body>
12378
        <div class="error-message">
12379
            $lang_not_exportable
12380
        </div>
12381
    </body>
12382
</html>
12383
EOD;
12384
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
12385
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
12386
        }
12387
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
12388
12389
        // Add the extra files that go along with a SCORM package.
12390
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
12391
12392
        $fs = new Filesystem();
12393
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
12394
12395
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
12396
        $manifest = @$xmldoc->saveXML();
12397
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
12398
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
12399
12400
        $htmlIndex = new LpIndexGenerator($this);
12401
12402
        file_put_contents(
12403
            $archivePath.'/'.$temp_dir_short.'/index.html',
12404
            $htmlIndex->generate()
12405
        );
12406
12407
        $zip_folder->add(
12408
            $archivePath.'/'.$temp_dir_short,
12409
            PCLZIP_OPT_REMOVE_PATH,
12410
            $archivePath.'/'.$temp_dir_short.'/'
12411
        );
12412
12413
        // Clean possible temporary files.
12414
        foreach ($files_cleanup as $file) {
12415
            $res = unlink($file);
12416
            if ($res === false) {
12417
                error_log(
12418
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
12419
                    0
12420
                );
12421
            }
12422
        }
12423
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
12424
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
12425
    }
12426
12427
    /**
12428
     * @param int $lp_id
12429
     *
12430
     * @return bool
12431
     */
12432
    public function scorm_export_to_pdf($lp_id)
12433
    {
12434
        $lp_id = (int) $lp_id;
12435
        $files_to_export = [];
12436
12437
        $sessionId = api_get_session_id();
12438
        $course_data = api_get_course_info($this->cc);
12439
12440
        if (!empty($course_data)) {
12441
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
12442
            $list = self::get_flat_ordered_items_list($lp_id);
12443
            if (!empty($list)) {
12444
                foreach ($list as $item_id) {
12445
                    $item = $this->items[$item_id];
12446
                    switch ($item->type) {
12447
                        case 'document':
12448
                            // Getting documents from a LP with chamilo documents
12449
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
12450
                            // Try loading document from the base course.
12451
                            if (empty($file_data) && !empty($sessionId)) {
12452
                                $file_data = DocumentManager::get_document_data_by_id(
12453
                                    $item->path,
12454
                                    $this->cc,
12455
                                    false,
12456
                                    0
12457
                                );
12458
                            }
12459
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
12460
                            if (file_exists($file_path)) {
12461
                                $files_to_export[] = [
12462
                                    'title' => $item->get_title(),
12463
                                    'path' => $file_path,
12464
                                ];
12465
                            }
12466
                            break;
12467
                        case 'asset': //commes from a scorm package generated by chamilo
12468
                        case 'sco':
12469
                            $file_path = $scorm_path.'/'.$item->path;
12470
                            if (file_exists($file_path)) {
12471
                                $files_to_export[] = [
12472
                                    'title' => $item->get_title(),
12473
                                    'path' => $file_path,
12474
                                ];
12475
                            }
12476
                            break;
12477
                        case 'dir':
12478
                            $files_to_export[] = [
12479
                                'title' => $item->get_title(),
12480
                                'path' => null,
12481
                            ];
12482
                            break;
12483
                    }
12484
                }
12485
            }
12486
12487
            $pdf = new PDF();
12488
            $result = $pdf->html_to_pdf(
12489
                $files_to_export,
12490
                $this->name,
12491
                $this->cc,
12492
                true,
12493
                true,
12494
                true,
12495
                $this->get_name()
12496
            );
12497
12498
            return $result;
12499
        }
12500
12501
        return false;
12502
    }
12503
12504
    /**
12505
     * Temp function to be moved in main_api or the best place around for this.
12506
     * Creates a file path if it doesn't exist.
12507
     *
12508
     * @param string $path
12509
     */
12510
    public function create_path($path)
12511
    {
12512
        $path_bits = explode('/', dirname($path));
12513
12514
        // IS_WINDOWS_OS has been defined in main_api.lib.php
12515
        $path_built = IS_WINDOWS_OS ? '' : '/';
12516
        foreach ($path_bits as $bit) {
12517
            if (!empty($bit)) {
12518
                $new_path = $path_built.$bit;
12519
                if (is_dir($new_path)) {
12520
                    $path_built = $new_path.'/';
12521
                } else {
12522
                    mkdir($new_path, api_get_permissions_for_new_directories());
12523
                    $path_built = $new_path.'/';
12524
                }
12525
            }
12526
        }
12527
    }
12528
12529
    /**
12530
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
12531
     *
12532
     * @return bool The results of the unlink function, or false if there was no image to start with
12533
     */
12534
    public function delete_lp_image()
12535
    {
12536
        $img = $this->get_preview_image();
12537
        if ($img != '') {
12538
            $del_file = $this->get_preview_image_path(null, 'sys');
12539
            if (isset($del_file) && file_exists($del_file)) {
12540
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
12541
                if (file_exists($del_file_2)) {
12542
                    unlink($del_file_2);
12543
                }
12544
                $this->set_preview_image('');
12545
12546
                return @unlink($del_file);
12547
            }
12548
        }
12549
12550
        return false;
12551
    }
12552
12553
    /**
12554
     * Uploads an author image to the upload/learning_path/images directory.
12555
     *
12556
     * @param array    The image array, coming from the $_FILES superglobal
12557
     *
12558
     * @return bool True on success, false on error
12559
     */
12560
    public function upload_image($image_array)
12561
    {
12562
        if (!empty($image_array['name'])) {
12563
            $upload_ok = process_uploaded_file($image_array);
12564
            $has_attachment = true;
12565
        }
12566
12567
        if ($upload_ok && $has_attachment) {
12568
            $courseDir = api_get_course_path().'/upload/learning_path/images';
12569
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
12570
            $updir = $sys_course_path.$courseDir;
12571
            // Try to add an extension to the file if it hasn't one.
12572
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
12573
12574
            if (filter_extension($new_file_name)) {
12575
                $file_extension = explode('.', $image_array['name']);
12576
                $file_extension = strtolower($file_extension[count($file_extension) - 1]);
12577
                $filename = uniqid('');
12578
                $new_file_name = $filename.'.'.$file_extension;
12579
                $new_path = $updir.'/'.$new_file_name;
12580
12581
                // Resize the image.
12582
                $temp = new Image($image_array['tmp_name']);
12583
                $temp->resize(104);
12584
                $result = $temp->send_image($new_path);
12585
12586
                // Storing the image filename.
12587
                if ($result) {
12588
                    $this->set_preview_image($new_file_name);
12589
12590
                    //Resize to 64px to use on course homepage
12591
                    $temp->resize(64);
12592
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
12593
12594
                    return true;
12595
                }
12596
            }
12597
        }
12598
12599
        return false;
12600
    }
12601
12602
    /**
12603
     * @param int    $lp_id
12604
     * @param string $status
12605
     */
12606
    public function set_autolaunch($lp_id, $status)
12607
    {
12608
        $course_id = api_get_course_int_id();
12609
        $lp_id = (int) $lp_id;
12610
        $status = (int) $status;
12611
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
12612
12613
        // Setting everything to autolaunch = 0
12614
        $attributes['autolaunch'] = 0;
12615
        $where = [
12616
            'session_id = ? AND c_id = ? ' => [
12617
                api_get_session_id(),
12618
                $course_id,
12619
            ],
12620
        ];
12621
        Database::update($lp_table, $attributes, $where);
12622
        if ($status == 1) {
12623
            //Setting my lp_id to autolaunch = 1
12624
            $attributes['autolaunch'] = 1;
12625
            $where = [
12626
                'iid = ? AND session_id = ? AND c_id = ?' => [
12627
                    $lp_id,
12628
                    api_get_session_id(),
12629
                    $course_id,
12630
                ],
12631
            ];
12632
            Database::update($lp_table, $attributes, $where);
12633
        }
12634
    }
12635
12636
    /**
12637
     * Gets previous_item_id for the next element of the lp_item table.
12638
     *
12639
     * @author Isaac flores paz
12640
     *
12641
     * @return int Previous item ID
12642
     */
12643
    public function select_previous_item_id()
12644
    {
12645
        $course_id = api_get_course_int_id();
12646
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12647
12648
        // Get the max order of the items
12649
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
12650
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
12651
        $rs_max_order = Database::query($sql);
12652
        $row_max_order = Database::fetch_object($rs_max_order);
12653
        $max_order = $row_max_order->display_order;
12654
        // Get the previous item ID
12655
        $sql = "SELECT iid as previous FROM $table_lp_item
12656
                WHERE
12657
                    c_id = $course_id AND
12658
                    lp_id = ".$this->lp_id." AND
12659
                    display_order = '$max_order' ";
12660
        $rs_max = Database::query($sql);
12661
        $row_max = Database::fetch_object($rs_max);
12662
12663
        // Return the previous item ID
12664
        return $row_max->previous;
12665
    }
12666
12667
    /**
12668
     * Copies an LP.
12669
     */
12670
    public function copy()
12671
    {
12672
        // Course builder
12673
        $cb = new CourseBuilder();
12674
12675
        //Setting tools that will be copied
12676
        $cb->set_tools_to_build(['learnpaths']);
12677
12678
        //Setting elements that will be copied
12679
        $cb->set_tools_specific_id_list(['learnpaths' => [$this->lp_id]]);
12680
12681
        $course = $cb->build($this->lp_session_id);
12682
12683
        //Course restorer
12684
        $course_restorer = new CourseRestorer($course);
12685
        $course_restorer->set_add_text_in_items(true);
12686
        $course_restorer->set_tool_copy_settings(
12687
            ['learnpaths' => ['reset_dates' => true]]
12688
        );
12689
        $course_restorer->restore(
12690
            api_get_course_id(),
12691
            api_get_session_id(),
12692
            false,
12693
            false
12694
        );
12695
    }
12696
12697
    /**
12698
     * Verify document size.
12699
     *
12700
     * @param string $s
12701
     *
12702
     * @return bool
12703
     */
12704
    public static function verify_document_size($s)
12705
    {
12706
        $post_max = ini_get('post_max_size');
12707
        if (substr($post_max, -1, 1) == 'M') {
12708
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
12709
        } elseif (substr($post_max, -1, 1) == 'G') {
12710
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
12711
        }
12712
        $upl_max = ini_get('upload_max_filesize');
12713
        if (substr($upl_max, -1, 1) == 'M') {
12714
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
12715
        } elseif (substr($upl_max, -1, 1) == 'G') {
12716
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
12717
        }
12718
        $documents_total_space = DocumentManager::documents_total_space();
12719
        $course_max_space = DocumentManager::get_course_quota();
12720
        $total_size = filesize($s) + $documents_total_space;
12721
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
12722
            return true;
12723
        }
12724
12725
        return false;
12726
    }
12727
12728
    /**
12729
     * Clear LP prerequisites.
12730
     */
12731
    public function clear_prerequisites()
12732
    {
12733
        $course_id = $this->get_course_int_id();
12734
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12735
        $lp_id = $this->get_id();
12736
        // Cleaning prerequisites
12737
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
12738
                WHERE c_id = $course_id AND lp_id = $lp_id";
12739
        Database::query($sql);
12740
12741
        // Cleaning mastery score for exercises
12742
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
12743
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
12744
        Database::query($sql);
12745
    }
12746
12747
    public function set_previous_step_as_prerequisite_for_all_items()
12748
    {
12749
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12750
        $course_id = $this->get_course_int_id();
12751
        $lp_id = $this->get_id();
12752
12753
        if (!empty($this->items)) {
12754
            $previous_item_id = null;
12755
            $previous_item_max = 0;
12756
            $previous_item_type = null;
12757
            $last_item_not_dir = null;
12758
            $last_item_not_dir_type = null;
12759
            $last_item_not_dir_max = null;
12760
12761
            foreach ($this->ordered_items as $itemId) {
12762
                $item = $this->getItem($itemId);
12763
                // if there was a previous item... (otherwise jump to set it)
12764
                if (!empty($previous_item_id)) {
12765
                    $current_item_id = $item->get_id(); //save current id
12766
                    if ($item->get_type() != 'dir') {
12767
                        // Current item is not a folder, so it qualifies to get a prerequisites
12768
                        if ($last_item_not_dir_type == 'quiz') {
12769
                            // if previous is quiz, mark its max score as default score to be achieved
12770
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
12771
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
12772
                            Database::query($sql);
12773
                        }
12774
                        // now simply update the prerequisite to set it to the last non-chapter item
12775
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
12776
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
12777
                        Database::query($sql);
12778
                        // record item as 'non-chapter' reference
12779
                        $last_item_not_dir = $item->get_id();
12780
                        $last_item_not_dir_type = $item->get_type();
12781
                        $last_item_not_dir_max = $item->get_max();
12782
                    }
12783
                } else {
12784
                    if ($item->get_type() != 'dir') {
12785
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
12786
                        $last_item_not_dir = $item->get_id();
12787
                        $last_item_not_dir_type = $item->get_type();
12788
                        $last_item_not_dir_max = $item->get_max();
12789
                    }
12790
                }
12791
                // Saving the item as "previous item" for the next loop
12792
                $previous_item_id = $item->get_id();
12793
                $previous_item_max = $item->get_max();
12794
                $previous_item_type = $item->get_type();
12795
            }
12796
        }
12797
    }
12798
12799
    /**
12800
     * @param array $params
12801
     *
12802
     * @throws \Doctrine\ORM\OptimisticLockException
12803
     *
12804
     * @return int
12805
     */
12806
    public static function createCategory($params)
12807
    {
12808
        $em = Database::getManager();
12809
        $item = new CLpCategory();
12810
        $item->setName($params['name']);
12811
        $item->setCId($params['c_id']);
12812
        $em->persist($item);
12813
        $em->flush();
12814
12815
        $id = $item->getId();
12816
12817
        $sessionId = api_get_session_id();
12818
        if (!empty($sessionId) && api_get_configuration_value('allow_session_lp_category')) {
12819
            $table = Database::get_course_table(TABLE_LP_CATEGORY);
12820
            $sql = "UPDATE $table SET session_id = $sessionId WHERE iid = $id";
12821
            Database::query($sql);
12822
        }
12823
12824
        api_item_property_update(
12825
            api_get_course_info(),
12826
            TOOL_LEARNPATH_CATEGORY,
12827
            $id,
12828
            'visible',
12829
            api_get_user_id()
12830
        );
12831
12832
        return $item->getId();
12833
    }
12834
12835
    /**
12836
     * @param array $params
12837
     */
12838
    public static function updateCategory($params)
12839
    {
12840
        $em = Database::getManager();
12841
        $item = self::getCategory($params['id']);
12842
12843
        if ($item) {
12844
            $item->setName($params['name']);
12845
            $em->persist($item);
12846
            $em->flush();
12847
        }
12848
    }
12849
12850
    /**
12851
     * @param int $id
12852
     */
12853
    public static function moveUpCategory($id)
12854
    {
12855
        $item = self::getCategory($id);
12856
        if ($item) {
12857
            $em = Database::getManager();
12858
            $position = $item->getPosition() - 1;
12859
            $item->setPosition($position);
12860
            $em->persist($item);
12861
            $em->flush();
12862
        }
12863
    }
12864
12865
    /**
12866
     * @param int $id
12867
     *
12868
     * @throws \Doctrine\ORM\ORMException
12869
     * @throws \Doctrine\ORM\OptimisticLockException
12870
     * @throws \Doctrine\ORM\TransactionRequiredException
12871
     */
12872
    public static function moveDownCategory($id)
12873
    {
12874
        $item = self::getCategory($id);
12875
        if ($item) {
12876
            $em = Database::getManager();
12877
            $position = $item->getPosition() + 1;
12878
            $item->setPosition($position);
12879
            $em->persist($item);
12880
            $em->flush();
12881
        }
12882
    }
12883
12884
    public static function getLpList($courseId, $sessionId, $onlyActiveLp = true)
12885
    {
12886
        $TABLE_LP = Database::get_course_table(TABLE_LP_MAIN);
12887
        $TABLE_ITEM_PROPERTY = Database::get_course_table(TABLE_ITEM_PROPERTY);
12888
        $courseId = (int) $courseId;
12889
        $sessionId = (int) $sessionId;
12890
12891
        $sql = "SELECT lp.id, lp.name
12892
                FROM $TABLE_LP lp
12893
                INNER JOIN $TABLE_ITEM_PROPERTY ip
12894
                ON lp.id = ip.ref
12895
                WHERE lp.c_id = $courseId ";
12896
12897
        if (!empty($sessionId)) {
12898
            $sql .= "AND ip.session_id = $sessionId ";
12899
        }
12900
12901
        if ($onlyActiveLp) {
12902
            $sql .= "AND ip.tool = 'learnpath' ";
12903
            $sql .= "AND ip.visibility = 1 ";
12904
        }
12905
12906
        $sql .= "GROUP BY lp.id";
12907
12908
        $result = Database::query($sql);
12909
12910
        return Database::store_result($result, 'ASSOC');
12911
    }
12912
12913
    /**
12914
     * @param int $courseId
12915
     *
12916
     * @throws \Doctrine\ORM\Query\QueryException
12917
     *
12918
     * @return int|mixed
12919
     */
12920
    public static function getCountCategories($courseId)
12921
    {
12922
        if (empty($courseId)) {
12923
            return 0;
12924
        }
12925
        $em = Database::getManager();
12926
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
12927
        $query->setParameter('id', $courseId);
12928
12929
        return $query->getSingleScalarResult();
12930
    }
12931
12932
    /**
12933
     * @param int $courseId
12934
     *
12935
     * @return array|CLpCategory[]
12936
     */
12937
    public static function getCategories($courseId, $withNoneCategory = false)
12938
    {
12939
        $em = Database::getManager();
12940
12941
        // Using doctrine extensions
12942
        /** @var SortableRepository $repo */
12943
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
12944
12945
        $categories = $repo->getBySortableGroupsQuery(['cId' => $courseId])->getResult();
12946
12947
        if ($withNoneCategory) {
12948
            $categoryTest = new CLpCategory();
12949
            $categoryTest->setId(0)
12950
                ->setName(get_lang('WithOutCategory'))
12951
                ->setPosition(0);
12952
12953
            array_unshift($categories, $categoryTest);
12954
        }
12955
12956
        return $categories;
12957
    }
12958
12959
    public static function getCategorySessionId($id)
12960
    {
12961
        if (false === api_get_configuration_value('allow_session_lp_category')) {
12962
            return 0;
12963
        }
12964
12965
        $table = Database::get_course_table(TABLE_LP_CATEGORY);
12966
        $id = (int) $id;
12967
12968
        $sql = "SELECT session_id FROM $table WHERE iid = $id";
12969
        $result = Database::query($sql);
12970
        $result = Database::fetch_array($result, 'ASSOC');
12971
12972
        if ($result) {
12973
            return (int) $result['session_id'];
12974
        }
12975
12976
        return 0;
12977
    }
12978
12979
    /**
12980
     * @param int $id
12981
     */
12982
    public static function getCategory($id): ?CLpCategory
12983
    {
12984
        $id = (int) $id;
12985
        $em = Database::getManager();
12986
12987
        return $em->find('ChamiloCourseBundle:CLpCategory', $id);
12988
    }
12989
12990
    /**
12991
     * @param int $courseId
12992
     *
12993
     * @return CLpCategory[]
12994
     */
12995
    public static function getCategoryByCourse($courseId)
12996
    {
12997
        $em = Database::getManager();
12998
12999
        return $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(['cId' => $courseId]);
13000
    }
13001
13002
    /**
13003
     * @param int $id
13004
     *
13005
     * @return bool
13006
     */
13007
    public static function deleteCategory($id)
13008
    {
13009
        $em = Database::getManager();
13010
        $id = (int) $id;
13011
        $item = self::getCategory($id);
13012
        if ($item) {
13013
            $courseId = $item->getCId();
13014
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
13015
            $query->setParameter('id', $courseId);
13016
            $query->setParameter('catId', $item->getId());
13017
            $lps = $query->getResult();
13018
13019
            // Setting category = 0.
13020
            if ($lps) {
13021
                foreach ($lps as $lpItem) {
13022
                    $lpItem->setCategoryId(0);
13023
                }
13024
            }
13025
13026
            if (api_get_configuration_value('allow_lp_subscription_to_usergroups')) {
13027
                $table = Database::get_course_table(TABLE_LP_CATEGORY_REL_USERGROUP);
13028
                $sql = "DELETE FROM $table
13029
                        WHERE
13030
                            lp_category_id = $id AND
13031
                            c_id = $courseId ";
13032
                Database::query($sql);
13033
            }
13034
13035
            // Removing category.
13036
            $em->remove($item);
13037
            $em->flush();
13038
13039
            $courseInfo = api_get_course_info_by_id($courseId);
13040
            $sessionId = api_get_session_id();
13041
13042
            // Delete link tool
13043
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
13044
            $linkLikeValue = "lp/lp_controller.php?%&action=view_category&id=$id%";
13045
            // Delete tools
13046
            $sql = "DELETE FROM $tbl_tool
13047
                    WHERE c_id = ".$courseId." AND (link LIKE '$linkLikeValue' AND image='lp_category.gif')";
13048
            Database::query($sql);
13049
13050
            return true;
13051
        }
13052
13053
        return false;
13054
    }
13055
13056
    /**
13057
     * @param int  $courseId
13058
     * @param bool $addSelectOption
13059
     *
13060
     * @return mixed
13061
     */
13062
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
13063
    {
13064
        $items = self::getCategoryByCourse($courseId);
13065
        $cats = [];
13066
        if ($addSelectOption) {
13067
            $cats = [get_lang('SelectACategory')];
13068
        }
13069
13070
        $sessionId = api_get_session_id();
13071
        $avoidCategoryInSession = false;
13072
        if (empty($sessionId)) {
13073
            $avoidCategoryInSession = true;
13074
        }
13075
        /*$checkSession = false;
13076
        if (api_get_configuration_value('allow_session_lp_category')) {
13077
            $checkSession = true;
13078
        }*/
13079
13080
        if (!empty($items)) {
13081
            foreach ($items as $cat) {
13082
                $categoryId = $cat->getId();
13083
                if ($avoidCategoryInSession) {
13084
                    $inSession = self::getCategorySessionId($categoryId);
13085
                    if (!empty($inSession)) {
13086
                        continue;
13087
                    }
13088
                }
13089
                $cats[$categoryId] = $cat->getName();
13090
            }
13091
        }
13092
13093
        return $cats;
13094
    }
13095
13096
    /**
13097
     * @param string $courseCode
13098
     * @param int    $lpId
13099
     * @param int    $user_id
13100
     *
13101
     * @return learnpath
13102
     */
13103
    public static function getLpFromSession($courseCode, $lpId, $user_id)
13104
    {
13105
        $debug = 0;
13106
        $learnPath = null;
13107
        $lpObject = Session::read('lpobject');
13108
        if ($lpObject !== null) {
13109
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
13110
            if ($debug) {
13111
                error_log('getLpFromSession: unserialize');
13112
                error_log('------getLpFromSession------');
13113
                error_log('------unserialize------');
13114
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
13115
                error_log("api_get_sessionid: ".api_get_session_id());
13116
            }
13117
        }
13118
13119
        if (!is_object($learnPath)) {
13120
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
13121
            if ($debug) {
13122
                error_log('------getLpFromSession------');
13123
                error_log('getLpFromSession: create new learnpath');
13124
                error_log("create new LP with $courseCode - $lpId - $user_id");
13125
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
13126
                error_log("api_get_sessionid: ".api_get_session_id());
13127
            }
13128
        }
13129
13130
        return $learnPath;
13131
    }
13132
13133
    /**
13134
     * @param int $itemId
13135
     *
13136
     * @return learnpathItem|false
13137
     */
13138
    public function getItem($itemId)
13139
    {
13140
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
13141
            return $this->items[$itemId];
13142
        }
13143
13144
        return false;
13145
    }
13146
13147
    /**
13148
     * @return int
13149
     */
13150
    public function getCurrentAttempt()
13151
    {
13152
        $attempt = $this->getItem($this->get_current_item_id());
13153
        if ($attempt) {
13154
            $attemptId = $attempt->get_attempt_id();
13155
13156
            return $attemptId;
13157
        }
13158
13159
        return 0;
13160
    }
13161
13162
    /**
13163
     * @return int
13164
     */
13165
    public function getCategoryId()
13166
    {
13167
        return (int) $this->categoryId;
13168
    }
13169
13170
    /**
13171
     * @param int $categoryId
13172
     *
13173
     * @return bool
13174
     */
13175
    public function setCategoryId($categoryId)
13176
    {
13177
        $this->categoryId = (int) $categoryId;
13178
        $table = Database::get_course_table(TABLE_LP_MAIN);
13179
        $lp_id = $this->get_id();
13180
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
13181
                WHERE iid = $lp_id";
13182
        Database::query($sql);
13183
13184
        return true;
13185
    }
13186
13187
    /**
13188
     * Get whether this is a learning path with the possibility to subscribe
13189
     * users or not.
13190
     *
13191
     * @return int
13192
     */
13193
    public function getSubscribeUsers()
13194
    {
13195
        return $this->subscribeUsers;
13196
    }
13197
13198
    /**
13199
     * Set whether this is a learning path with the possibility to subscribe
13200
     * users or not.
13201
     *
13202
     * @param int $value (0 = false, 1 = true)
13203
     *
13204
     * @return bool
13205
     */
13206
    public function setSubscribeUsers($value)
13207
    {
13208
        $this->subscribeUsers = (int) $value;
13209
        $table = Database::get_course_table(TABLE_LP_MAIN);
13210
        $lp_id = $this->get_id();
13211
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
13212
                WHERE iid = $lp_id";
13213
        Database::query($sql);
13214
13215
        return true;
13216
    }
13217
13218
    /**
13219
     * Calculate the count of stars for a user in this LP
13220
     * This calculation is based on the following rules:
13221
     * - the student gets one star when he gets to 50% of the learning path
13222
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
13223
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
13224
     * - the student gets the final star when the score for the *last* test is >= 80%.
13225
     *
13226
     * @param int $sessionId Optional. The session ID
13227
     *
13228
     * @return int The count of stars
13229
     */
13230
    public function getCalculateStars($sessionId = 0)
13231
    {
13232
        $stars = 0;
13233
        $progress = self::getProgress(
13234
            $this->lp_id,
13235
            $this->user_id,
13236
            $this->course_int_id,
13237
            $sessionId
13238
        );
13239
13240
        if ($progress >= 50) {
13241
            $stars++;
13242
        }
13243
13244
        // Calculate stars chapters evaluation
13245
        $exercisesItems = $this->getExercisesItems();
13246
13247
        if (!empty($exercisesItems)) {
13248
            $totalResult = 0;
13249
13250
            foreach ($exercisesItems as $exerciseItem) {
13251
                $exerciseResultInfo = Event::getExerciseResultsByUser(
13252
                    $this->user_id,
13253
                    $exerciseItem->path,
13254
                    $this->course_int_id,
13255
                    $sessionId,
13256
                    $this->lp_id,
13257
                    $exerciseItem->db_id
13258
                );
13259
13260
                $exerciseResultInfo = end($exerciseResultInfo);
13261
13262
                if (!$exerciseResultInfo) {
13263
                    continue;
13264
                }
13265
13266
                if (!empty($exerciseResultInfo['exe_weighting'])) {
13267
                    $exerciseResult = $exerciseResultInfo['exe_result'] * 100 / $exerciseResultInfo['exe_weighting'];
13268
                } else {
13269
                    $exerciseResult = 0;
13270
                }
13271
                $totalResult += $exerciseResult;
13272
            }
13273
13274
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
13275
13276
            if ($totalExerciseAverage >= 50) {
13277
                $stars++;
13278
            }
13279
13280
            if ($totalExerciseAverage >= 80) {
13281
                $stars++;
13282
            }
13283
        }
13284
13285
        // Calculate star for final evaluation
13286
        $finalEvaluationItem = $this->getFinalEvaluationItem();
13287
13288
        if (!empty($finalEvaluationItem)) {
13289
            $evaluationResultInfo = Event::getExerciseResultsByUser(
13290
                $this->user_id,
13291
                $finalEvaluationItem->path,
13292
                $this->course_int_id,
13293
                $sessionId,
13294
                $this->lp_id,
13295
                $finalEvaluationItem->db_id
13296
            );
13297
13298
            $evaluationResultInfo = end($evaluationResultInfo);
13299
13300
            if ($evaluationResultInfo) {
13301
                $evaluationResult = $evaluationResultInfo['exe_result'] * 100 / $evaluationResultInfo['exe_weighting'];
13302
13303
                if ($evaluationResult >= 80) {
13304
                    $stars++;
13305
                }
13306
            }
13307
        }
13308
13309
        return $stars;
13310
    }
13311
13312
    /**
13313
     * Get the items of exercise type.
13314
     *
13315
     * @return array The items. Otherwise return false
13316
     */
13317
    public function getExercisesItems()
13318
    {
13319
        $exercises = [];
13320
        foreach ($this->items as $item) {
13321
            if ($item->type != 'quiz') {
13322
                continue;
13323
            }
13324
            $exercises[] = $item;
13325
        }
13326
13327
        array_pop($exercises);
13328
13329
        return $exercises;
13330
    }
13331
13332
    /**
13333
     * Get the item of exercise type (evaluation type).
13334
     *
13335
     * @return array The final evaluation. Otherwise return false
13336
     */
13337
    public function getFinalEvaluationItem()
13338
    {
13339
        $exercises = [];
13340
        foreach ($this->items as $item) {
13341
            if ($item->type != 'quiz') {
13342
                continue;
13343
            }
13344
13345
            $exercises[] = $item;
13346
        }
13347
13348
        return array_pop($exercises);
13349
    }
13350
13351
    /**
13352
     * Calculate the total points achieved for the current user in this learning path.
13353
     *
13354
     * @param int $sessionId Optional. The session Id
13355
     *
13356
     * @return int
13357
     */
13358
    public function getCalculateScore($sessionId = 0)
13359
    {
13360
        // Calculate stars chapters evaluation
13361
        $exercisesItems = $this->getExercisesItems();
13362
        $finalEvaluationItem = $this->getFinalEvaluationItem();
13363
        $totalExercisesResult = 0;
13364
        $totalEvaluationResult = 0;
13365
13366
        if ($exercisesItems !== false) {
13367
            foreach ($exercisesItems as $exerciseItem) {
13368
                $exerciseResultInfo = Event::getExerciseResultsByUser(
13369
                    $this->user_id,
13370
                    $exerciseItem->path,
13371
                    $this->course_int_id,
13372
                    $sessionId,
13373
                    $this->lp_id,
13374
                    $exerciseItem->db_id
13375
                );
13376
13377
                $exerciseResultInfo = end($exerciseResultInfo);
13378
13379
                if (!$exerciseResultInfo) {
13380
                    continue;
13381
                }
13382
13383
                $totalExercisesResult += $exerciseResultInfo['exe_result'];
13384
            }
13385
        }
13386
13387
        if (!empty($finalEvaluationItem)) {
13388
            $evaluationResultInfo = Event::getExerciseResultsByUser(
13389
                $this->user_id,
13390
                $finalEvaluationItem->path,
13391
                $this->course_int_id,
13392
                $sessionId,
13393
                $this->lp_id,
13394
                $finalEvaluationItem->db_id
13395
            );
13396
13397
            $evaluationResultInfo = end($evaluationResultInfo);
13398
13399
            if ($evaluationResultInfo) {
13400
                $totalEvaluationResult += $evaluationResultInfo['exe_result'];
13401
            }
13402
        }
13403
13404
        return $totalExercisesResult + $totalEvaluationResult;
13405
    }
13406
13407
    /**
13408
     * Check if URL is not allowed to be show in a iframe.
13409
     *
13410
     * @param string $src
13411
     *
13412
     * @return string
13413
     */
13414
    public function fixBlockedLinks($src)
13415
    {
13416
        $urlInfo = parse_url($src);
13417
13418
        $platformProtocol = 'https';
13419
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
13420
            $platformProtocol = 'http';
13421
        }
13422
13423
        $protocolFixApplied = false;
13424
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
13425
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
13426
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
13427
13428
        if ($platformProtocol != $scheme) {
13429
            Session::write('x_frame_source', $src);
13430
            $src = 'blank.php?error=x_frames_options';
13431
            $protocolFixApplied = true;
13432
        }
13433
13434
        if ($protocolFixApplied == false) {
13435
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
13436
                // Check X-Frame-Options
13437
                $ch = curl_init();
13438
                $options = [
13439
                    CURLOPT_URL => $src,
13440
                    CURLOPT_RETURNTRANSFER => true,
13441
                    CURLOPT_HEADER => true,
13442
                    CURLOPT_FOLLOWLOCATION => true,
13443
                    CURLOPT_ENCODING => "",
13444
                    CURLOPT_AUTOREFERER => true,
13445
                    CURLOPT_CONNECTTIMEOUT => 120,
13446
                    CURLOPT_TIMEOUT => 120,
13447
                    CURLOPT_MAXREDIRS => 10,
13448
                ];
13449
13450
                $proxySettings = api_get_configuration_value('proxy_settings');
13451
                if (!empty($proxySettings) &&
13452
                    isset($proxySettings['curl_setopt_array'])
13453
                ) {
13454
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
13455
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
13456
                }
13457
13458
                curl_setopt_array($ch, $options);
13459
                $response = curl_exec($ch);
13460
                $httpCode = curl_getinfo($ch);
13461
                $headers = substr($response, 0, $httpCode['header_size']);
13462
13463
                $error = false;
13464
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
13465
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
13466
                ) {
13467
                    $error = true;
13468
                }
13469
13470
                if ($error) {
13471
                    Session::write('x_frame_source', $src);
13472
                    $src = 'blank.php?error=x_frames_options';
13473
                }
13474
            }
13475
        }
13476
13477
        return $src;
13478
    }
13479
13480
    /**
13481
     * Check if this LP has a created forum in the basis course.
13482
     *
13483
     * @return bool
13484
     */
13485
    public function lpHasForum()
13486
    {
13487
        $forumTable = Database::get_course_table(TABLE_FORUM);
13488
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
13489
13490
        $fakeFrom = "
13491
            $forumTable f
13492
            INNER JOIN $itemProperty ip
13493
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
13494
        ";
13495
13496
        $resultData = Database::select(
13497
            'COUNT(f.iid) AS qty',
13498
            $fakeFrom,
13499
            [
13500
                'where' => [
13501
                    'ip.visibility != ? AND ' => 2,
13502
                    'ip.tool = ? AND ' => TOOL_FORUM,
13503
                    'f.c_id = ? AND ' => intval($this->course_int_id),
13504
                    'f.lp_id = ?' => intval($this->lp_id),
13505
                ],
13506
            ],
13507
            'first'
13508
        );
13509
13510
        return $resultData['qty'] > 0;
13511
    }
13512
13513
    /**
13514
     * Get the forum for this learning path.
13515
     *
13516
     * @param int $sessionId
13517
     *
13518
     * @return bool
13519
     */
13520
    public function getForum($sessionId = 0)
13521
    {
13522
        $forumTable = Database::get_course_table(TABLE_FORUM);
13523
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
13524
13525
        $fakeFrom = "$forumTable f
13526
            INNER JOIN $itemProperty ip ";
13527
13528
        if ($this->lp_session_id == 0) {
13529
            $fakeFrom .= "
13530
                ON (
13531
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND (
13532
                        f.session_id = ip.session_id OR ip.session_id IS NULL
13533
                    )
13534
                )
13535
            ";
13536
        } else {
13537
            $fakeFrom .= "
13538
                ON (
13539
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND f.session_id = ip.session_id
13540
                )
13541
            ";
13542
        }
13543
13544
        $resultData = Database::select(
13545
            'f.*',
13546
            $fakeFrom,
13547
            [
13548
                'where' => [
13549
                    'ip.visibility != ? AND ' => 2,
13550
                    'ip.tool = ? AND ' => TOOL_FORUM,
13551
                    'f.session_id = ? AND ' => $sessionId,
13552
                    'f.c_id = ? AND ' => intval($this->course_int_id),
13553
                    'f.lp_id = ?' => intval($this->lp_id),
13554
                ],
13555
            ],
13556
            'first'
13557
        );
13558
13559
        if (empty($resultData)) {
13560
            return false;
13561
        }
13562
13563
        return $resultData;
13564
    }
13565
13566
    /**
13567
     * Create a forum for this learning path.
13568
     *
13569
     * @param int $forumCategoryId
13570
     *
13571
     * @return int The forum ID if was created. Otherwise return false
13572
     */
13573
    public function createForum($forumCategoryId)
13574
    {
13575
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
13576
13577
        $forumId = store_forum(
13578
            [
13579
                'lp_id' => $this->lp_id,
13580
                'forum_title' => $this->name,
13581
                'forum_comment' => null,
13582
                'forum_category' => (int) $forumCategoryId,
13583
                'students_can_edit_group' => ['students_can_edit' => 0],
13584
                'allow_new_threads_group' => ['allow_new_threads' => 0],
13585
                'default_view_type_group' => ['default_view_type' => 'flat'],
13586
                'group_forum' => 0,
13587
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
13588
            ],
13589
            [],
13590
            true
13591
        );
13592
13593
        return $forumId;
13594
    }
13595
13596
    /**
13597
     * Get the LP Final Item form.
13598
     *
13599
     * @throws Exception
13600
     * @throws HTML_QuickForm_Error
13601
     *
13602
     * @return string
13603
     */
13604
    public function getFinalItemForm()
13605
    {
13606
        $finalItem = $this->getFinalItem();
13607
        $title = '';
13608
13609
        if ($finalItem) {
13610
            $title = $finalItem->get_title();
13611
            $buttonText = get_lang('Save');
13612
            $content = $this->getSavedFinalItem();
13613
        } else {
13614
            $buttonText = get_lang('LPCreateDocument');
13615
            $content = $this->getFinalItemTemplate();
13616
        }
13617
13618
        $courseInfo = api_get_course_info();
13619
        $result = $this->generate_lp_folder($courseInfo);
13620
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
13621
        $relative_prefix = '../../';
13622
13623
        $editorConfig = [
13624
            'ToolbarSet' => 'LearningPathDocuments',
13625
            'Width' => '100%',
13626
            'Height' => '500',
13627
            'FullPage' => true,
13628
            'CreateDocumentDir' => $relative_prefix,
13629
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
13630
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
13631
        ];
13632
13633
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
13634
            'type' => 'document',
13635
            'lp_id' => $this->lp_id,
13636
        ]);
13637
13638
        $form = new FormValidator('final_item', 'POST', $url);
13639
        $form->addText('title', get_lang('Title'));
13640
        $form->addButtonSave($buttonText);
13641
13642
        $renderer = $form->defaultRenderer();
13643
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
13644
13645
        $form->addHtmlEditor(
13646
            'content_lp_certificate',
13647
            null,
13648
            true,
13649
            false,
13650
            $editorConfig,
13651
            true
13652
        );
13653
        $form->addHtml(
13654
            Display::return_message(
13655
                get_lang('LPEndStepAddTagsToShowCertificateOrSkillAutomatically').'</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
13656
                'normal',
13657
                false
13658
            )
13659
        );
13660
        $form->addHidden('action', 'add_final_item');
13661
        $form->addHidden('path', Session::read('pathItem'));
13662
        $form->addHidden('previous', $this->get_last());
13663
        $form->setDefaults(['title' => $title, 'content_lp_certificate' => $content]);
13664
13665
        if ($form->validate()) {
13666
            $values = $form->exportValues();
13667
            $lastItemId = $this->getLastInFirstLevel();
13668
13669
            if (!$finalItem) {
13670
                $documentId = $this->create_document(
13671
                    $this->course_info,
13672
                    $values['content_lp_certificate'],
13673
                    $values['title']
13674
                );
13675
                $this->add_item(
13676
                    0,
13677
                    $lastItemId,
13678
                    'final_item',
13679
                    $documentId,
13680
                    $values['title'],
13681
                    ''
13682
                );
13683
13684
                Display::addFlash(
13685
                    Display::return_message(get_lang('Added'))
13686
                );
13687
            } else {
13688
                $this->edit_document($this->course_info);
13689
            }
13690
        }
13691
13692
        return $form->returnForm();
13693
    }
13694
13695
    /**
13696
     * Check if the current lp item is first, both, last or none from lp list.
13697
     *
13698
     * @param int $currentItemId
13699
     *
13700
     * @return string
13701
     */
13702
    public function isFirstOrLastItem($currentItemId)
13703
    {
13704
        $lpItemId = [];
13705
        $typeListNotToVerify = self::getChapterTypes();
13706
13707
        // Using get_toc() function instead $this->items because returns the correct order of the items
13708
        foreach ($this->get_toc() as $item) {
13709
            if (!in_array($item['type'], $typeListNotToVerify)) {
13710
                $lpItemId[] = $item['id'];
13711
            }
13712
        }
13713
13714
        $lastLpItemIndex = count($lpItemId) - 1;
13715
        $position = array_search($currentItemId, $lpItemId);
13716
13717
        switch ($position) {
13718
            case 0:
13719
                if (!$lastLpItemIndex) {
13720
                    $answer = 'both';
13721
                    break;
13722
                }
13723
13724
                $answer = 'first';
13725
                break;
13726
            case $lastLpItemIndex:
13727
                $answer = 'last';
13728
                break;
13729
            default:
13730
                $answer = 'none';
13731
        }
13732
13733
        return $answer;
13734
    }
13735
13736
    /**
13737
     * Get whether this is a learning path with the accumulated SCORM time or not.
13738
     *
13739
     * @return int
13740
     */
13741
    public function getAccumulateScormTime()
13742
    {
13743
        return $this->accumulateScormTime;
13744
    }
13745
13746
    /**
13747
     * Set whether this is a learning path with the accumulated SCORM time or not.
13748
     *
13749
     * @param int $value (0 = false, 1 = true)
13750
     *
13751
     * @return bool Always returns true
13752
     */
13753
    public function setAccumulateScormTime($value)
13754
    {
13755
        $this->accumulateScormTime = (int) $value;
13756
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
13757
        $lp_id = $this->get_id();
13758
        $sql = "UPDATE $lp_table
13759
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
13760
                WHERE iid = $lp_id";
13761
        Database::query($sql);
13762
13763
        return true;
13764
    }
13765
13766
    /**
13767
     * Returns an HTML-formatted link to a resource, to incorporate directly into
13768
     * the new learning path tool.
13769
     *
13770
     * The function is a big switch on tool type.
13771
     * In each case, we query the corresponding table for information and build the link
13772
     * with that information.
13773
     *
13774
     * @author Yannick Warnier <[email protected]> - rebranding based on
13775
     * previous work (display_addedresource_link_in_learnpath())
13776
     *
13777
     * @param int $course_id      Course code
13778
     * @param int $learningPathId The learning path ID (in lp table)
13779
     * @param int $id_in_path     the unique index in the items table
13780
     * @param int $lpViewId
13781
     * @param int $lpSessionId
13782
     *
13783
     * @return string
13784
     */
13785
    public static function rl_get_resource_link_for_learnpath(
13786
        $course_id,
13787
        $learningPathId,
13788
        $id_in_path,
13789
        $lpViewId,
13790
        $lpSessionId = 0
13791
    ) {
13792
        $session_id = api_get_session_id();
13793
        $course_info = api_get_course_info_by_id($course_id);
13794
13795
        $learningPathId = (int) $learningPathId;
13796
        $id_in_path = (int) $id_in_path;
13797
        $lpViewId = (int) $lpViewId;
13798
13799
        $em = Database::getManager();
13800
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
13801
13802
        /** @var CLpItem $rowItem */
13803
        $rowItem = $lpItemRepo->findOneBy([
13804
            'cId' => $course_id,
13805
            'lpId' => $learningPathId,
13806
            'iid' => $id_in_path,
13807
        ]);
13808
13809
        if (!$rowItem) {
13810
            // Try one more time with "id"
13811
            /** @var CLpItem $rowItem */
13812
            $rowItem = $lpItemRepo->findOneBy([
13813
                'cId' => $course_id,
13814
                'lpId' => $learningPathId,
13815
                'id' => $id_in_path,
13816
            ]);
13817
13818
            if (!$rowItem) {
13819
                return -1;
13820
            }
13821
        }
13822
13823
        $course_code = $course_info['code'];
13824
        $type = $rowItem->getItemType();
13825
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
13826
        $main_dir_path = api_get_path(WEB_CODE_PATH);
13827
        $main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
13828
        $link = '';
13829
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
13830
        // It adds lti parameter
13831
        if (isset($_REQUEST['lti_launch_id'])) {
13832
            $extraParams .= '&lti_launch_id='.Security::remove_XSS($_REQUEST['lti_launch_id']);
13833
        }
13834
13835
        switch ($type) {
13836
            case 'dir':
13837
                return $main_dir_path.'lp/blank.php';
13838
            case TOOL_CALENDAR_EVENT:
13839
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
13840
            case TOOL_ANNOUNCEMENT:
13841
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
13842
            case TOOL_LINK:
13843
                $linkInfo = Link::getLinkInfo($id);
13844
                if ($linkInfo) {
13845
                    $itemPropertyInfo = api_get_item_property_info($course_id, TOOL_LINK, $id, $session_id);
13846
                    if ($itemPropertyInfo && 0 === (int) $itemPropertyInfo['visibility']) {
13847
                        return '';
13848
                    }
13849
                    if (isset($linkInfo['url'])) {
13850
                        return $linkInfo['url'];
13851
                    }
13852
                }
13853
13854
                return '';
13855
            case TOOL_QUIZ:
13856
                if (empty($id)) {
13857
                    return '';
13858
                }
13859
13860
                // Get the lp_item_view with the highest view_count.
13861
                $learnpathItemViewResult = $em
13862
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
13863
                    ->findBy(
13864
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
13865
                        ['viewCount' => 'DESC'],
13866
                        1
13867
                    );
13868
                /** @var CLpItemView $learnpathItemViewData */
13869
                $learnpathItemViewData = current($learnpathItemViewResult);
13870
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
13871
13872
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
13873
                    .http_build_query([
13874
                        'lp_init' => 1,
13875
                        'learnpath_item_view_id' => (int) $learnpathItemViewId,
13876
                        'learnpath_id' => (int) $learningPathId,
13877
                        'learnpath_item_id' => (int) $id_in_path,
13878
                        'exerciseId' => (int) $id,
13879
                    ]);
13880
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
13881
                $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
13882
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
13883
                $myrow = Database::fetch_array($result);
13884
                $path = $myrow['path'];
13885
13886
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
13887
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
13888
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;
13889
            case TOOL_FORUM:
13890
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
13891
            case TOOL_THREAD:
13892
                // forum post
13893
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
13894
                if (empty($id)) {
13895
                    return '';
13896
                }
13897
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
13898
                $result = Database::query($sql);
13899
                $myrow = Database::fetch_array($result);
13900
13901
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
13902
                    .$extraParams;
13903
            case TOOL_POST:
13904
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13905
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
13906
                $myrow = Database::fetch_array($result);
13907
13908
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
13909
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
13910
            case TOOL_READOUT_TEXT:
13911
                return api_get_path(WEB_CODE_PATH).
13912
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
13913
            case TOOL_DOCUMENT:
13914
                $repo = $em->getRepository('ChamiloCourseBundle:CDocument');
13915
                $document = $repo->findOneBy(['cId' => $course_id, 'iid' => $id]);
13916
13917
                if (empty($document)) {
13918
                    // Try with normal id
13919
                    $document = $repo->findOneBy(['cId' => $course_id, 'id' => $id]);
13920
13921
                    if (empty($document)) {
13922
                        return '';
13923
                    }
13924
                }
13925
13926
                $documentPathInfo = pathinfo($document->getPath());
13927
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v', 'webm', 'wav'];
13928
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
13929
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
13930
13931
                $openmethod = 2;
13932
                $officedoc = false;
13933
                Session::write('openmethod', $openmethod);
13934
                Session::write('officedoc', $officedoc);
13935
13936
                // Same validation as in document/download.php
13937
                $isVisible = DocumentManager::is_visible(
13938
                    $document->getPath(),
13939
                    api_get_course_info(),
13940
                    api_get_session_id()
13941
                );
13942
13943
                if (!api_is_allowed_to_edit() && !$isVisible) {
13944
                    return '';
13945
                }
13946
13947
                if ($showDirectUrl) {
13948
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
13949
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
13950
                        if (Link::isPdfLink($file)) {
13951
                            return api_get_path(WEB_LIBRARY_PATH).
13952
                                'javascript/ViewerJS/index.html?zoom=page-width#'.$file;
13953
                        }
13954
                    }
13955
13956
                    return $file;
13957
                }
13958
13959
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
13960
            case TOOL_LP_FINAL_ITEM:
13961
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
13962
                    .$extraParams;
13963
            case 'assignments':
13964
                return $main_dir_path.'work/work.php?'.$extraParams;
13965
            case TOOL_DROPBOX:
13966
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
13967
            case 'introduction_text': //DEPRECATED
13968
                return '';
13969
            case TOOL_COURSE_DESCRIPTION:
13970
                return $main_dir_path.'course_description?'.$extraParams;
13971
            case TOOL_GROUP:
13972
                return $main_dir_path.'group/group.php?'.$extraParams;
13973
            case TOOL_USER:
13974
                return $main_dir_path.'user/user.php?'.$extraParams;
13975
            case TOOL_STUDENTPUBLICATION:
13976
                if (!empty($rowItem->getPath())) {
13977
                    $workId = $rowItem->getPath();
13978
                    if (empty($lpSessionId) && !empty($session_id)) {
13979
                        // Check if a student publication with the same name exists in this session see BT#17700
13980
                        $title = Database::escape_string($rowItem->getTitle());
13981
                        $table = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
13982
                        $sql = "SELECT * FROM $table
13983
                                WHERE
13984
                                    active = 1 AND
13985
                                    parent_id = 0 AND
13986
                                    c_id = $course_id AND
13987
                                    session_id = $session_id AND
13988
                                    title = '$title'
13989
                                LIMIT 1";
13990
                        $result = Database::query($sql);
13991
                        if (Database::num_rows($result)) {
13992
                            $work = Database::fetch_array($result, 'ASSOC');
13993
                            if ($work) {
13994
                                $workId = $work['iid'];
13995
                            }
13996
                        }
13997
                    }
13998
13999
                    return $main_dir_path.'work/work_list.php?id='.$workId.'&'.$extraParams;
14000
                }
14001
14002
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
14003
            case TOOL_XAPI:
14004
                $toolLaunch = $em->find(ToolLaunch::class, $id);
14005
14006
                if (empty($toolLaunch)) {
14007
                    break;
14008
                }
14009
14010
                return api_get_path(WEB_PLUGIN_PATH).'xapi/'
14011
                    .('cmi5' === $toolLaunch->getActivityType() ? 'cmi5/view.php' : 'tincan/view.php')
14012
                    ."?id=$id&$extraParams";
14013
            case TOOL_H5P:
14014
                if ('true' === api_get_plugin_setting('h5pimport', 'tool_enable')) {
14015
                    $toolLaunch = $em->find(H5pImport::class, $id);
14016
14017
                    if (empty($toolLaunch)) {
14018
                        break;
14019
                    }
14020
14021
                    return api_get_path(WEB_PLUGIN_PATH).'h5pimport/view.php'."?id=$id&$extraParams";
14022
                }
14023
                break;
14024
            case TOOL_SURVEY:
14025
                $table = Database::get_course_table(TABLE_SURVEY);
14026
                $sql = "SELECT code FROM $table WHERE c_id = $course_id AND survey_id =".(int) $id;
14027
                $result = Database::query($sql);
14028
                $surveyCode = Database::result($result, 0, 0);
14029
                $autoSurveyLink = SurveyUtil::generateFillSurveyLink(
14030
                    'auto',
14031
                    $course_info,
14032
                    api_get_session_id(),
14033
                    $surveyCode
14034
                );
14035
                $lpParams = [
14036
                    'lp_id' => $learningPathId,
14037
                    'lp_item_id' => $id_in_path,
14038
                    'origin' => 'learnpath',
14039
                ];
14040
14041
                return $autoSurveyLink.'&'.http_build_query($lpParams);
14042
        }
14043
14044
        return $link;
14045
    }
14046
14047
    /**
14048
     * Gets the name of a resource (generally used in learnpath when no name is provided).
14049
     *
14050
     * @author Yannick Warnier <[email protected]>
14051
     *
14052
     * @param string $course_code    Course code
14053
     * @param int    $learningPathId
14054
     * @param int    $id_in_path     The resource ID
14055
     *
14056
     * @return string
14057
     */
14058
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
14059
    {
14060
        $_course = api_get_course_info($course_code);
14061
        if (empty($_course)) {
14062
            return '';
14063
        }
14064
        $course_id = $_course['real_id'];
14065
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
14066
        $learningPathId = (int) $learningPathId;
14067
        $id_in_path = (int) $id_in_path;
14068
14069
        $sql = "SELECT item_type, title, ref
14070
                FROM $tbl_lp_item
14071
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
14072
        $res_item = Database::query($sql);
14073
14074
        if (Database::num_rows($res_item) < 1) {
14075
            return '';
14076
        }
14077
        $row_item = Database::fetch_array($res_item);
14078
        $type = strtolower($row_item['item_type']);
14079
        $id = $row_item['ref'];
14080
        $output = '';
14081
14082
        switch ($type) {
14083
            case TOOL_CALENDAR_EVENT:
14084
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
14085
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
14086
                $myrow = Database::fetch_array($result);
14087
                $output = $myrow['title'];
14088
                break;
14089
            case TOOL_ANNOUNCEMENT:
14090
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
14091
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
14092
                $myrow = Database::fetch_array($result);
14093
                $output = $myrow['title'];
14094
                break;
14095
            case TOOL_LINK:
14096
                // Doesn't take $target into account.
14097
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
14098
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
14099
                $myrow = Database::fetch_array($result);
14100
                $output = $myrow['title'];
14101
                break;
14102
            case TOOL_QUIZ:
14103
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
14104
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE iid = $id");
14105
                $myrow = Database::fetch_array($result);
14106
                $output = $myrow['title'];
14107
                break;
14108
            case TOOL_FORUM:
14109
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
14110
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
14111
                $myrow = Database::fetch_array($result);
14112
                $output = $myrow['forum_name'];
14113
                break;
14114
            case TOOL_THREAD:
14115
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
14116
                // Grabbing the title of the post.
14117
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
14118
                $result_title = Database::query($sql_title);
14119
                $myrow_title = Database::fetch_array($result_title);
14120
                $output = $myrow_title['post_title'];
14121
                break;
14122
            case TOOL_POST:
14123
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
14124
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
14125
                $result = Database::query($sql);
14126
                $post = Database::fetch_array($result);
14127
                $output = $post['post_title'];
14128
                break;
14129
            case 'dir':
14130
            case TOOL_DOCUMENT:
14131
                $title = $row_item['title'];
14132
                $output = '-';
14133
                if (!empty($title)) {
14134
                    $output = $title;
14135
                }
14136
                break;
14137
            case 'hotpotatoes':
14138
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
14139
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
14140
                $myrow = Database::fetch_array($result);
14141
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
14142
                $last = count($pathname) - 1; // Making a correct name for the link.
14143
                $filename = $pathname[$last]; // Making a correct name for the link.
14144
                $myrow['path'] = rawurlencode($myrow['path']);
14145
                $output = $filename;
14146
                break;
14147
        }
14148
14149
        return stripslashes($output);
14150
    }
14151
14152
    /**
14153
     * Get the parent names for the current item.
14154
     *
14155
     * @param int $newItemId Optional. The item ID
14156
     *
14157
     * @return array
14158
     */
14159
    public function getCurrentItemParentNames($newItemId = 0)
14160
    {
14161
        $newItemId = $newItemId ?: $this->get_current_item_id();
14162
        $return = [];
14163
        $item = $this->getItem($newItemId);
14164
        $parent = $this->getItem($item->get_parent());
14165
14166
        while ($parent) {
14167
            $return[] = $parent->get_title();
14168
            $parent = $this->getItem($parent->get_parent());
14169
        }
14170
14171
        return array_reverse($return);
14172
    }
14173
14174
    /**
14175
     * Reads and process "lp_subscription_settings" setting.
14176
     *
14177
     * @return array
14178
     */
14179
    public static function getSubscriptionSettings()
14180
    {
14181
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
14182
        if (empty($subscriptionSettings)) {
14183
            // By default allow both settings
14184
            $subscriptionSettings = [
14185
                'allow_add_users_to_lp' => true,
14186
                'allow_add_users_to_lp_category' => true,
14187
            ];
14188
        } else {
14189
            $subscriptionSettings = $subscriptionSettings['options'];
14190
        }
14191
14192
        return $subscriptionSettings;
14193
    }
14194
14195
    /**
14196
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
14197
     */
14198
    public function exportToCourseBuildFormat()
14199
    {
14200
        if (!api_is_allowed_to_edit()) {
14201
            return false;
14202
        }
14203
14204
        $courseBuilder = new CourseBuilder();
14205
        $itemList = [];
14206
        /** @var learnpathItem $item */
14207
        foreach ($this->items as $item) {
14208
            $itemList[$item->get_type()][] = $item->get_path();
14209
        }
14210
14211
        if (empty($itemList)) {
14212
            return false;
14213
        }
14214
14215
        if (isset($itemList['document'])) {
14216
            // Get parents
14217
            foreach ($itemList['document'] as $documentId) {
14218
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
14219
                if (!empty($documentInfo['parents'])) {
14220
                    foreach ($documentInfo['parents'] as $parentInfo) {
14221
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
14222
                            continue;
14223
                        }
14224
                        $itemList['document'][] = $parentInfo['iid'];
14225
                    }
14226
                }
14227
            }
14228
14229
            $courseInfo = api_get_course_info();
14230
            foreach ($itemList['document'] as $documentId) {
14231
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
14232
                $items = DocumentManager::get_resources_from_source_html(
14233
                    $documentInfo['absolute_path'],
14234
                    true,
14235
                    TOOL_DOCUMENT
14236
                );
14237
14238
                if (!empty($items)) {
14239
                    foreach ($items as $item) {
14240
                        // Get information about source url
14241
                        $url = $item[0]; // url
14242
                        $scope = $item[1]; // scope (local, remote)
14243
                        $type = $item[2]; // type (rel, abs, url)
14244
14245
                        $origParseUrl = parse_url($url);
14246
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
14247
14248
                        if ($scope == 'local') {
14249
                            if ($type == 'abs' || $type == 'rel') {
14250
                                $documentFile = strstr($realOrigPath, 'document');
14251
                                if (strpos($realOrigPath, $documentFile) !== false) {
14252
                                    $documentFile = str_replace('document', '', $documentFile);
14253
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
14254
                                    // Document found! Add it to the list
14255
                                    if ($itemDocumentId) {
14256
                                        $itemList['document'][] = $itemDocumentId;
14257
                                    }
14258
                                }
14259
                            }
14260
                        }
14261
                    }
14262
                }
14263
            }
14264
14265
            $courseBuilder->build_documents(
14266
                api_get_session_id(),
14267
                $this->get_course_int_id(),
14268
                true,
14269
                $itemList['document']
14270
            );
14271
        }
14272
14273
        if (isset($itemList['quiz'])) {
14274
            $courseBuilder->build_quizzes(
14275
                api_get_session_id(),
14276
                $this->get_course_int_id(),
14277
                true,
14278
                $itemList['quiz']
14279
            );
14280
        }
14281
14282
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
14283
14284
        /*if (!empty($itemList['thread'])) {
14285
            $postList = [];
14286
            foreach ($itemList['thread'] as $postId) {
14287
                $post = get_post_information($postId);
14288
                if ($post) {
14289
                    if (!isset($itemList['forum'])) {
14290
                        $itemList['forum'] = [];
14291
                    }
14292
                    $itemList['forum'][] = $post['forum_id'];
14293
                    $postList[] = $postId;
14294
                }
14295
            }
14296
14297
            if (!empty($postList)) {
14298
                $courseBuilder->build_forum_posts(
14299
                    $this->get_course_int_id(),
14300
                    null,
14301
                    null,
14302
                    $postList
14303
                );
14304
            }
14305
        }*/
14306
14307
        if (!empty($itemList['thread'])) {
14308
            $threadList = [];
14309
            $em = Database::getManager();
14310
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
14311
            foreach ($itemList['thread'] as $threadId) {
14312
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
14313
                $thread = $repo->find($threadId);
14314
                if ($thread) {
14315
                    $itemList['forum'][] = $thread->getForumId();
14316
                    $threadList[] = $thread->getIid();
14317
                }
14318
            }
14319
14320
            if (!empty($threadList)) {
14321
                $courseBuilder->build_forum_topics(
14322
                    api_get_session_id(),
14323
                    $this->get_course_int_id(),
14324
                    null,
14325
                    $threadList
14326
                );
14327
            }
14328
        }
14329
14330
        $forumCategoryList = [];
14331
        if (isset($itemList['forum'])) {
14332
            foreach ($itemList['forum'] as $forumId) {
14333
                $forumInfo = get_forums($forumId);
14334
                $forumCategoryList[] = $forumInfo['forum_category'];
14335
            }
14336
        }
14337
14338
        if (!empty($forumCategoryList)) {
14339
            $courseBuilder->build_forum_category(
14340
                api_get_session_id(),
14341
                $this->get_course_int_id(),
14342
                true,
14343
                $forumCategoryList
14344
            );
14345
        }
14346
14347
        if (!empty($itemList['forum'])) {
14348
            $courseBuilder->build_forums(
14349
                api_get_session_id(),
14350
                $this->get_course_int_id(),
14351
                true,
14352
                $itemList['forum']
14353
            );
14354
        }
14355
14356
        if (isset($itemList['link'])) {
14357
            $courseBuilder->build_links(
14358
                api_get_session_id(),
14359
                $this->get_course_int_id(),
14360
                true,
14361
                $itemList['link']
14362
            );
14363
        }
14364
14365
        if (!empty($itemList['student_publication'])) {
14366
            $courseBuilder->build_works(
14367
                api_get_session_id(),
14368
                $this->get_course_int_id(),
14369
                true,
14370
                $itemList['student_publication']
14371
            );
14372
        }
14373
14374
        $courseBuilder->build_learnpaths(
14375
            api_get_session_id(),
14376
            $this->get_course_int_id(),
14377
            true,
14378
            [$this->get_id()],
14379
            false
14380
        );
14381
14382
        $courseBuilder->restoreDocumentsFromList();
14383
14384
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
14385
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
14386
        $result = DocumentManager::file_send_for_download(
14387
            $zipPath,
14388
            true,
14389
            $this->get_name().'.zip'
14390
        );
14391
14392
        if ($result) {
14393
            api_not_allowed();
14394
        }
14395
14396
        return true;
14397
    }
14398
14399
    /**
14400
     * Get whether this is a learning path with the accumulated work time or not.
14401
     *
14402
     * @return int
14403
     */
14404
    public function getAccumulateWorkTime()
14405
    {
14406
        return (int) $this->accumulateWorkTime;
14407
    }
14408
14409
    /**
14410
     * Get whether this is a learning path with the accumulated work time or not.
14411
     *
14412
     * @return int
14413
     */
14414
    public function getAccumulateWorkTimeTotalCourse()
14415
    {
14416
        $table = Database::get_course_table(TABLE_LP_MAIN);
14417
        $sql = "SELECT SUM(accumulate_work_time) AS total
14418
                FROM $table
14419
                WHERE c_id = ".$this->course_int_id;
14420
        $result = Database::query($sql);
14421
        $row = Database::fetch_array($result);
14422
14423
        return (int) $row['total'];
14424
    }
14425
14426
    /**
14427
     * Set whether this is a learning path with the accumulated work time or not.
14428
     *
14429
     * @param int $value (0 = false, 1 = true)
14430
     *
14431
     * @return bool
14432
     */
14433
    public function setAccumulateWorkTime($value)
14434
    {
14435
        if (!api_get_configuration_value('lp_minimum_time')) {
14436
            return false;
14437
        }
14438
14439
        $this->accumulateWorkTime = (int) $value;
14440
        $table = Database::get_course_table(TABLE_LP_MAIN);
14441
        $lp_id = $this->get_id();
14442
        $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
14443
                WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
14444
        Database::query($sql);
14445
14446
        return true;
14447
    }
14448
14449
    /**
14450
     * @param int $lpId
14451
     * @param int $courseId
14452
     *
14453
     * @return mixed
14454
     */
14455
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
14456
    {
14457
        $lpId = (int) $lpId;
14458
        $courseId = (int) $courseId;
14459
14460
        $table = Database::get_course_table(TABLE_LP_MAIN);
14461
        $sql = "SELECT accumulate_work_time
14462
                FROM $table
14463
                WHERE c_id = $courseId AND id = $lpId";
14464
        $result = Database::query($sql);
14465
        $row = Database::fetch_array($result);
14466
14467
        return $row['accumulate_work_time'];
14468
    }
14469
14470
    /**
14471
     * @param int $courseId
14472
     *
14473
     * @return int
14474
     */
14475
    public static function getAccumulateWorkTimeTotal($courseId)
14476
    {
14477
        $table = Database::get_course_table(TABLE_LP_MAIN);
14478
        $courseId = (int) $courseId;
14479
        $sql = "SELECT SUM(accumulate_work_time) AS total
14480
                FROM $table
14481
                WHERE c_id = $courseId";
14482
        $result = Database::query($sql);
14483
        $row = Database::fetch_array($result);
14484
14485
        return (int) $row['total'];
14486
    }
14487
14488
    /**
14489
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
14490
     * and put the images in.
14491
     *
14492
     * @return array
14493
     */
14494
    public static function getIconSelect()
14495
    {
14496
        $theme = api_get_visual_theme();
14497
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
14498
        $icons = ['' => get_lang('SelectAnOption')];
14499
14500
        if (is_dir($path)) {
14501
            $finder = new Finder();
14502
            $finder->files()->in($path);
14503
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
14504
            /** @var SplFileInfo $file */
14505
            foreach ($finder as $file) {
14506
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
14507
                    $icons[$file->getFilename()] = $file->getFilename();
14508
                }
14509
            }
14510
        }
14511
14512
        return $icons;
14513
    }
14514
14515
    /**
14516
     * Get the learnpaths availables for next lp.
14517
     *
14518
     * @param int $courseId
14519
     *
14520
     * @return array
14521
     */
14522
    public static function getNextLpsAvailable($courseId, $lpId)
14523
    {
14524
        $table = Database::get_course_table(TABLE_LP_MAIN);
14525
14526
        $sqlNextLp = "SELECT next_lp_id FROM $table WHERE c_id = $courseId AND next_lp_id > 0";
14527
        $sql = "SELECT iid, name
14528
                FROM $table
14529
            WHERE c_id = $courseId AND
14530
                  iid NOT IN($sqlNextLp)";
14531
        $rs = Database::query($sql);
14532
        $lpsAvailable = [0 => get_lang('None')];
14533
        if (Database::num_rows($rs) > 0) {
14534
            while ($row = Database::fetch_array($rs)) {
14535
                if ($row['iid'] == $lpId) {
14536
                    continue;
14537
                }
14538
                $lpsAvailable[$row['iid']] = $row['name'];
14539
            }
14540
        }
14541
14542
        return $lpsAvailable;
14543
    }
14544
14545
    /**
14546
     * Get The next lp id.
14547
     *
14548
     * @param $lpId
14549
     * @param $courseId
14550
     *
14551
     * @return int
14552
     */
14553
    public static function getFlowNextLpId($lpId, $courseId)
14554
    {
14555
        $table = Database::get_course_table(TABLE_LP_MAIN);
14556
14557
        $sql = "SELECT next_lp_id
14558
                FROM $table
14559
            WHERE c_id = $courseId AND iid = $lpId";
14560
        $rs = Database::query($sql);
14561
        $nextLpId = Database::result($rs, 0, 0);
14562
14563
        return $nextLpId;
14564
    }
14565
14566
    /**
14567
     * Get The previous lp id.
14568
     *
14569
     * @param $nextLpId
14570
     * @param $courseId
14571
     *
14572
     * @return int
14573
     */
14574
    public static function getFlowPrevLpId($nextLpId, $courseId)
14575
    {
14576
        $table = Database::get_course_table(TABLE_LP_MAIN);
14577
14578
        $sql = "SELECT iid
14579
                FROM $table
14580
            WHERE c_id = $courseId AND next_lp_id = $nextLpId";
14581
        $rs = Database::query($sql);
14582
        $prevLpId = Database::result($rs, 0, 0);
14583
14584
        return $prevLpId;
14585
    }
14586
14587
    /**
14588
     * Save the next lp id.
14589
     *
14590
     * @param $lpId
14591
     * @param $nextLpId
14592
     */
14593
    public static function saveTheNextLp($lpId, $nextLpId)
14594
    {
14595
        $nextLpId = (int) $nextLpId;
14596
        $table = Database::get_course_table(TABLE_LP_MAIN);
14597
        Database::query("UPDATE $table SET next_lp_id = $nextLpId WHERE iid = $lpId");
14598
    }
14599
14600
    /**
14601
     * @param int $lpId
14602
     *
14603
     * @return string
14604
     */
14605
    public static function getSelectedIcon($lpId)
14606
    {
14607
        $extraFieldValue = new ExtraFieldValue('lp');
14608
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
14609
        $icon = '';
14610
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
14611
            $icon = $lpIcon['value'];
14612
        }
14613
14614
        return $icon;
14615
    }
14616
14617
    /**
14618
     * @param int $lpId
14619
     *
14620
     * @return string
14621
     */
14622
    public static function getSelectedIconHtml($lpId)
14623
    {
14624
        $icon = self::getSelectedIcon($lpId);
14625
14626
        if (empty($icon)) {
14627
            return '';
14628
        }
14629
14630
        $theme = api_get_visual_theme();
14631
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
14632
14633
        return Display::img($path);
14634
    }
14635
14636
    /**
14637
     * @param string $value
14638
     *
14639
     * @return string
14640
     */
14641
    public function cleanItemTitle($value)
14642
    {
14643
        $value = Security::remove_XSS(strip_tags($value));
14644
14645
        return $value;
14646
    }
14647
14648
    public function setItemTitle(FormValidator $form)
14649
    {
14650
        if (api_get_configuration_value('save_titles_as_html')) {
14651
            $form->addHtmlEditor(
14652
                'title',
14653
                get_lang('Title'),
14654
                true,
14655
                false,
14656
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
14657
            );
14658
        } else {
14659
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
14660
            $form->applyFilter('title', 'trim');
14661
            $form->applyFilter('title', 'html_filter');
14662
        }
14663
    }
14664
14665
    /**
14666
     * @return array
14667
     */
14668
    public function getItemsForForm($addParentCondition = false)
14669
    {
14670
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
14671
        $course_id = api_get_course_int_id();
14672
14673
        $sql = "SELECT * FROM $tbl_lp_item
14674
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
14675
14676
        if ($addParentCondition) {
14677
            $sql .= ' AND parent_item_id = 0 ';
14678
        }
14679
        $sql .= ' ORDER BY display_order ASC';
14680
14681
        $result = Database::query($sql);
14682
        $arrLP = [];
14683
        while ($row = Database::fetch_array($result)) {
14684
            $arrLP[] = [
14685
                'iid' => $row['iid'],
14686
                'id' => $row['iid'],
14687
                'item_type' => $row['item_type'],
14688
                'title' => $this->cleanItemTitle($row['title']),
14689
                'title_raw' => $row['title'],
14690
                'path' => $row['path'],
14691
                'description' => Security::remove_XSS($row['description']),
14692
                'parent_item_id' => $row['parent_item_id'],
14693
                'previous_item_id' => $row['previous_item_id'],
14694
                'next_item_id' => $row['next_item_id'],
14695
                'display_order' => $row['display_order'],
14696
                'max_score' => $row['max_score'],
14697
                'min_score' => $row['min_score'],
14698
                'mastery_score' => $row['mastery_score'],
14699
                'prerequisite' => $row['prerequisite'],
14700
                'max_time_allowed' => $row['max_time_allowed'],
14701
                'prerequisite_min_score' => $row['prerequisite_min_score'],
14702
                'prerequisite_max_score' => $row['prerequisite_max_score'],
14703
            ];
14704
        }
14705
14706
        return $arrLP;
14707
    }
14708
14709
    /**
14710
     * Gets whether this SCORM learning path has been marked to use the score
14711
     * as progress. Takes into account whether the learnpath matches (SCORM
14712
     * content + less than 2 items).
14713
     *
14714
     * @return bool True if the score should be used as progress, false otherwise
14715
     */
14716
    public function getUseScoreAsProgress()
14717
    {
14718
        // If not a SCORM, we don't care about the setting
14719
        if ($this->get_type() != 2) {
14720
            return false;
14721
        }
14722
        // If more than one step in the SCORM, we don't care about the setting
14723
        if ($this->get_total_items_count() > 1) {
14724
            return false;
14725
        }
14726
        $extraFieldValue = new ExtraFieldValue('lp');
14727
        $doUseScore = false;
14728
        $useScore = $extraFieldValue->get_values_by_handler_and_field_variable($this->get_id(), 'use_score_as_progress');
14729
        if (!empty($useScore) && isset($useScore['value'])) {
14730
            $doUseScore = $useScore['value'];
14731
        }
14732
14733
        return $doUseScore;
14734
    }
14735
14736
    /**
14737
     * Get the user identifier (user_id or username
14738
     * Depends on scorm_api_username_as_student_id in app/config/configuration.php.
14739
     *
14740
     * @return string User ID or username, depending on configuration setting
14741
     */
14742
    public static function getUserIdentifierForExternalServices()
14743
    {
14744
        if (api_get_configuration_value('scorm_api_username_as_student_id')) {
14745
            return api_get_user_info(api_get_user_id())['username'];
14746
        } elseif (api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id') != null) {
14747
            $extraFieldValue = new ExtraFieldValue('user');
14748
            $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'));
14749
14750
            return $extrafield['value'];
14751
        } else {
14752
            return api_get_user_id();
14753
        }
14754
    }
14755
14756
    /**
14757
     * Save the new order for learning path items.
14758
     *
14759
     * We have to update parent_item_id, previous_item_id, next_item_id, display_order in the database.
14760
     *
14761
     * @param array $orderList A associative array with item ID as key and parent ID as value.
14762
     * @param int   $courseId
14763
     */
14764
    public static function sortItemByOrderList(array $orderList, $courseId = 0)
14765
    {
14766
        $courseId = $courseId ?: api_get_course_int_id();
14767
        $itemList = new LpItemOrderList();
14768
14769
        foreach ($orderList as $id => $parentId) {
14770
            $item = new LpOrderItem($id, $parentId);
14771
            $itemList->add($item);
14772
        }
14773
14774
        $parents = $itemList->getListOfParents();
14775
14776
        foreach ($parents as $parentId) {
14777
            $sameParentLpItemList = $itemList->getItemWithSameParent($parentId);
14778
            $previous_item_id = 0;
14779
            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...
14780
                $item_id = $sameParentLpItemList->list[$i]->id;
14781
                // display_order
14782
                $display_order = $i + 1;
14783
                $itemList->setParametersForId($item_id, $display_order, 'display_order');
14784
                // previous_item_id
14785
                $itemList->setParametersForId($item_id, $previous_item_id, 'previous_item_id');
14786
                $previous_item_id = $item_id;
14787
                // next_item_id
14788
                $next_item_id = 0;
14789
                if ($i < count($sameParentLpItemList->list) - 1) {
14790
                    $next_item_id = $sameParentLpItemList->list[$i + 1]->id;
14791
                }
14792
                $itemList->setParametersForId($item_id, $next_item_id, 'next_item_id');
14793
            }
14794
        }
14795
14796
        $table = Database::get_course_table(TABLE_LP_ITEM);
14797
14798
        foreach ($itemList->list as $item) {
14799
            $params = [];
14800
            $params['display_order'] = $item->display_order;
14801
            $params['previous_item_id'] = $item->previous_item_id;
14802
            $params['next_item_id'] = $item->next_item_id;
14803
            $params['parent_item_id'] = $item->parent_item_id;
14804
14805
            Database::update(
14806
                $table,
14807
                $params,
14808
                [
14809
                    'iid = ? AND c_id = ? ' => [
14810
                        (int) $item->id,
14811
                        (int) $courseId,
14812
                    ],
14813
                ]
14814
            );
14815
        }
14816
    }
14817
14818
    public static function findLastView(
14819
        int $lpId,
14820
        int $studentId,
14821
        int $courseId,
14822
        int $sessionId = 0,
14823
        bool $createIfNotExists = false
14824
    ): array {
14825
        $tblLpView = Database::get_course_table(TABLE_LP_VIEW);
14826
14827
        $sessionCondition = api_get_session_condition($sessionId);
14828
14829
        $sql = "SELECT iid FROM $tblLpView
14830
            WHERE c_id = $courseId AND lp_id = $lpId AND user_id = $studentId $sessionCondition
14831
            ORDER BY view_count DESC";
14832
        $result = Database::query($sql);
14833
14834
        $lpView = Database::fetch_assoc($result);
14835
14836
        if ($createIfNotExists && empty($lpView)) {
14837
            $lpViewId = Database::insert(
14838
                $tblLpView,
14839
                [
14840
                    'c_id' => $courseId,
14841
                    'lp_id' => $lpId,
14842
                    'user_id' => $studentId,
14843
                    'view_count' => 1,
14844
                    'session_id' => $sessionId,
14845
                ]
14846
            );
14847
            Database::update($tblLpView, ['id' => $lpViewId], ['iid = ?' => $lpViewId]);
14848
14849
            return ['iid' => $lpViewId];
14850
        }
14851
14852
        return empty($lpView) ? [] : $lpView;
14853
    }
14854
14855
    /**
14856
     * Check and obtain the lp final item if exist.
14857
     *
14858
     * @return learnpathItem
14859
     */
14860
    public function getFinalItem()
14861
    {
14862
        if (empty($this->items)) {
14863
            return null;
14864
        }
14865
14866
        foreach ($this->items as $item) {
14867
            if ($item->type !== 'final_item') {
14868
                continue;
14869
            }
14870
14871
            return $item;
14872
        }
14873
    }
14874
14875
    /**
14876
     * Get the depth level of LP item.
14877
     *
14878
     * @param array $items
14879
     * @param int   $currentItemId
14880
     *
14881
     * @return int
14882
     */
14883
    private static function get_level_for_item($items, $currentItemId)
14884
    {
14885
        $parentItemId = 0;
14886
        if (isset($items[$currentItemId])) {
14887
            $parentItemId = $items[$currentItemId]->parent;
14888
        }
14889
14890
        if ($parentItemId == 0) {
14891
            return 0;
14892
        } else {
14893
            return self::get_level_for_item($items, $parentItemId) + 1;
14894
        }
14895
    }
14896
14897
    /**
14898
     * Generate the link for a learnpath category as course tool.
14899
     *
14900
     * @param int $categoryId
14901
     *
14902
     * @return string
14903
     */
14904
    private static function getCategoryLinkForTool($categoryId)
14905
    {
14906
        $categoryId = (int) $categoryId;
14907
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
14908
            .http_build_query(
14909
                [
14910
                    'action' => 'view_category',
14911
                    'id' => $categoryId,
14912
                ]
14913
            );
14914
14915
        return $link;
14916
    }
14917
14918
    /**
14919
     * Return the scorm item type object with spaces replaced with _
14920
     * The return result is use to build a css classname like scorm_type_$return.
14921
     *
14922
     * @param $in_type
14923
     *
14924
     * @return mixed
14925
     */
14926
    private static function format_scorm_type_item($in_type)
14927
    {
14928
        return str_replace(' ', '_', $in_type);
14929
    }
14930
14931
    /**
14932
     * Get the LP Final Item Template.
14933
     *
14934
     * @return string
14935
     */
14936
    private function getFinalItemTemplate()
14937
    {
14938
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
14939
    }
14940
14941
    /**
14942
     * Get the LP Final Item Url.
14943
     *
14944
     * @return string
14945
     */
14946
    private function getSavedFinalItem()
14947
    {
14948
        $finalItem = $this->getFinalItem();
14949
        $doc = DocumentManager::get_document_data_by_id(
14950
            $finalItem->path,
14951
            $this->cc
14952
        );
14953
        if ($doc && file_exists($doc['absolute_path'])) {
14954
            return file_get_contents($doc['absolute_path']);
14955
        }
14956
14957
        return '';
14958
    }
14959
14960
    /**
14961
     * Gets the form to evaluate if it exists contains the extra field extra_authorlpitem
14962
     * to establish authors when editing an item of an LP.
14963
     */
14964
    private function setAuthorLpItem(FormValidator $form)
14965
    {
14966
        if ($form->hasElement('extra_authorlpitem')) {
14967
            /** @var HTML_QuickForm_select $author */
14968
            $author = $form->getElement('extra_authorlpitem');
14969
            $options = [];
14970
            $field = new ExtraField('user');
14971
            $authorLp = $field->get_handler_field_info_by_field_variable('authorlp');
14972
            $extraFieldId = isset($authorLp['id']) ? (int) $authorLp['id'] : 0;
14973
            if ($extraFieldId != 0) {
14974
                $extraFieldValueUser = new ExtraFieldValue('user');
14975
                $values = $extraFieldValueUser->get_item_id_from_field_variable_and_field_value(
14976
                    $authorLp['variable'],
14977
                    1,
14978
                    true,
14979
                    false,
14980
                    true
14981
                );
14982
14983
                if (!empty($values)) {
14984
                    foreach ($values as $item) {
14985
                        $teacher = api_get_user_info($item['item_id']);
14986
                        $options[$teacher['id']] = $teacher['complete_name'];
14987
                    }
14988
                }
14989
            }
14990
            $author->setOptions($options);
14991
        }
14992
    }
14993
}
14994