Passed
Push — 1.11.x ( 08587c...c85e98 )
by Yannick
10:54
created

learnpath   F

Complexity

Total Complexity 2054

Size/Duplication

Total Lines 14899
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
wmc 2054
eloc 8265
c 5
b 0
f 0
dl 0
loc 14899
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 getHideTableOfContents() 0 3 1
A get_type_static() 0 16 3
A getChapterTypes() 0 4 1
A get_flat_ordered_items_list() 0 35 5
A set_attempt_mode() 0 32 5
B get_attempt_mode() 0 21 9
A getProgressBar() 0 5 1
B update_default_view_mode() 0 33 6
A next() 0 22 5
B get_iv_objectives_array() 0 38 6
A get_total_items_count() 0 3 1
F save_last() 0 89 23
A get_first_item_id() 0 8 2
B isUserSubscribedToLp() 0 51 8
A get_update_queue() 0 3 1
F get_link() 0 326 56
A switch_attempt_mode() 0 16 4
F prerequisites_match() 0 80 19
A previous() 0 11 1
A tree_array() 0 4 1
B move_down() 0 54 8
A open() 0 9 1
F edit_item() 0 235 22
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 get_last() 0 10 2
A categoryIsPublished() 0 24 2
A get_type() 0 8 3
A get_maker() 0 7 2
A get_progress_bar() 0 12 1
A getLpNameById() 0 8 1
A 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
F add_item() 0 238 17
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
B get_progress_bar_text() 0 58 11
A get_teacher_toc_buttons() 0 21 4
A get_js_lib() 0 8 2
B getTOCTree() 0 44 8
F first() 0 71 20
B create_tree_array() 0 38 11
A get_items_details_as_js() 0 8 2
A get_progress_bar_mode() 0 7 2
B set_terms_by_prefix() 0 68 10
A get_user_id() 0 7 2
A set_use_max_score() 0 12 1
A toggle_visibility() 0 18 3
A get_current_item_id() 0 8 2
D prerequistesDatesMatch() 0 43 21
A set_previous_item() 0 6 2
A save_current() 0 32 6
A getLastInFirstLevel() 0 13 2
A set_theme() 0 11 1
F __construct() 0 317 51
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 close() 0 13 2
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
F autocomplete_parents() 0 101 17
B move_up() 0 51 8
A get_interactions_count_from_db() 0 16 2
B get_scorm_prereq_string() 0 73 11
B get_scorm_xml_node() 0 19 8
A has_audio() 0 11 3
A set_encoding() 0 19 4
B edit_item_prereq() 0 41 7
A get_id() 0 7 2
A get_next_item_id() 0 10 3
B get_view() 0 49 7
F is_lp_visible_for_student() 0 102 20
A getProgress() 0 23 2
A getFlowLpbuttons() 0 24 6
B get_complete_items_count() 0 40 8
A get_objectives_count_from_db() 0 16 2
B delete_item() 0 70 6
D move_item() 0 125 18
A getTotalItemsCountWithoutDirs() 0 16 4
A update_scorm_debug() 0 23 4
A toggleCategoryVisibility() 0 14 2
C getParentToc() 0 54 13
F add_lp() 0 151 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 73 10
A set_hide_toc_frame() 0 15 2
B get_next_index() 0 23 7
D getPackageType() 0 90 20
A get_common_index_terms_by_prefix() 0 17 3
A get_lp_session_id() 0 7 2
A get_previous_item_id() 0 5 1
A get_toc() 0 18 2
F categoryIsVisibleForStudent() 0 89 19
F delete() 0 120 19
A set_maker() 0 14 2
A get_name() 0 7 2
A getStatusCSSClassName() 0 7 2
A delete_children_items() 0 22 4
B save_item() 0 43 8
A set_name() 0 32 3
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 display_lp_prerequisites_list() 0 31 5
F display_link_form() 0 189 36
F display_document_form() 0 368 76
A getForum() 0 44 3
A getExercisesItems() 0 13 3
B getFinalItemForm() 0 89 4
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
F display_item_form() 0 239 38
A select_previous_item_id() 0 22 1
A returnLpItemList() 0 15 2
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
A getSubscribeUsers() 0 3 1
A deleteCategory() 0 47 5
B generate_lp_folder() 0 57 8
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
B print_recursive() 0 43 11
A clear_prerequisites() 0 14 1
C scorm_export_to_pdf() 0 70 12
B isFirstOrLastItem() 0 32 6
B display_move_item() 0 52 11
C getCalculateStars() 0 80 12
F display_hotpotatoes_form() 0 155 36
B get_js_dropdown_array() 0 70 6
A getFinalEvaluationItem() 0 12 3
F build_action_menu() 0 179 15
A getCategoryByCourse() 0 5 1
A getSurveys() 0 34 3
B display_resources() 0 89 5
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
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 setAccumulateScormTime() 0 11 1
A get_extension() 0 5 1
A getLpFromSession() 0 28 5
A generate_learning_path_folder() 0 28 4
A getCategories() 0 20 2
A return_new_tree() 0 34 4
A setCategoryId() 0 10 1
A get_student_publications() 0 41 3
A lpHasForum() 0 26 1
F display_student_publication_form() 0 167 32
F processBuildMenuElements() 0 447 56
A saveTheNextLp() 0 5 1
A getUserIdentifierForExternalServices() 0 11 3
A getAccumulateWorkTime() 0 3 1
A getNextLpsAvailable() 0 21 4
A getFlowPrevLpId() 0 11 1
A getSelectedIcon() 0 10 3
B sortItemByOrderList() 0 48 7
A getAccumulateWorkTimePrerequisite() 0 13 1
A getSelectedIconHtml() 0 12 2
A getItemsForForm() 0 39 3
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 getAccumulateWorkTimeTotalCourse() 0 10 1
A setItemTitle() 0 14 2
A getCategoryLinkForTool() 0 12 1
A getSubscriptionSettings() 0 14 2
A setAuthorLpItem() 0 27 6
A getUseScoreAsProgress() 0 18 5
A getFinalItem() 0 12 4
A getFinalItemTemplate() 0 3 1
A getAccumulateWorkTimeTotal() 0 11 1
A findLastView() 0 35 4
A getFlowNextLpId() 0 11 1
A getIconSelect() 0 19 4
F exportToCourseBuildFormat() 0 199 30
A get_level_for_item() 0 11 3
A setAccumulateWorkTime() 0 14 2
F rl_get_resource_link_for_learnpath() 0 260 51

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
891
                return $newLp->getIid();
892
        }
893
    }
894
895
    /**
896
     * Auto completes the parents of an item in case it's been completed or passed.
897
     *
898
     * @param int $item Optional ID of the item from which to look for parents
899
     */
900
    public function autocomplete_parents($item)
901
    {
902
        $debug = $this->debug;
903
904
        if (empty($item)) {
905
            $item = $this->current;
906
        }
907
908
        $currentItem = $this->getItem($item);
909
        if ($currentItem) {
910
            $parent_id = $currentItem->get_parent();
911
            $parent = $this->getItem($parent_id);
912
            if ($parent) {
913
                // if $item points to an object and there is a parent.
914
                if ($debug) {
915
                    error_log(
916
                        'Autocompleting parent of item '.$item.' '.
917
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
918
                        0
919
                    );
920
                }
921
922
                // New experiment including failed and browsed in completed status.
923
                //$current_status = $currentItem->get_status();
924
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
925
                // Fixes chapter auto complete
926
                if (true) {
927
                    // If the current item is completed or passes or succeeded.
928
                    $updateParentStatus = true;
929
                    if ($debug) {
930
                        error_log('Status of current item is alright');
931
                    }
932
933
                    foreach ($parent->get_children() as $childItemId) {
934
                        $childItem = $this->getItem($childItemId);
935
936
                        // If children was not set try to get the info
937
                        if (empty($childItem->db_item_view_id)) {
938
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
939
                        }
940
941
                        // Check all his brothers (parent's children) for completion status.
942
                        if ($childItemId != $item) {
943
                            if ($debug) {
944
                                error_log(
945
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
946
                                    0
947
                                );
948
                            }
949
                            // Trying completing parents of failed and browsed items as well.
950
                            if ($childItem->status_is(
951
                                [
952
                                    'completed',
953
                                    'passed',
954
                                    'succeeded',
955
                                    'browsed',
956
                                    'failed',
957
                                ]
958
                            )
959
                            ) {
960
                                // Keep completion status to true.
961
                                continue;
962
                            } else {
963
                                if ($debug > 2) {
964
                                    error_log(
965
                                        '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,
966
                                        0
967
                                    );
968
                                }
969
                                $updateParentStatus = false;
970
                                break;
971
                            }
972
                        }
973
                    }
974
975
                    if ($updateParentStatus) {
976
                        // If all the children were completed:
977
                        $parent->set_status('completed');
978
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
979
                        // Force the status to "completed"
980
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
981
                        $this->update_queue[$parent->get_id()] = 'completed';
982
                        if ($debug) {
983
                            error_log(
984
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
985
                                print_r($this->update_queue, 1),
986
                                0
987
                            );
988
                        }
989
                        // Recursive call.
990
                        $this->autocomplete_parents($parent->get_id());
991
                    }
992
                }
993
            } else {
994
                if ($debug) {
995
                    error_log("Parent #$parent_id does not exists");
996
                }
997
            }
998
        } else {
999
            if ($debug) {
1000
                error_log("#$item is an item that doesn't have parents");
1001
            }
1002
        }
1003
    }
1004
1005
    /**
1006
     * Closes the current resource.
1007
     *
1008
     * Stops the timer
1009
     * Saves into the database if required
1010
     * Clears the current resource data from this object
1011
     *
1012
     * @return bool True on success, false on failure
1013
     */
1014
    public function close()
1015
    {
1016
        if (empty($this->lp_id)) {
1017
            $this->error = 'Trying to close this learnpath but no ID is set';
1018
1019
            return false;
1020
        }
1021
        $this->current_time_stop = time();
1022
        $this->ordered_items = [];
1023
        $this->index = 0;
1024
        unset($this->lp_id);
1025
        //unset other stuff
1026
        return true;
1027
    }
1028
1029
    /**
1030
     * Static admin function allowing removal of a learnpath.
1031
     *
1032
     * @param array  $courseInfo
1033
     * @param int    $id         Learnpath ID
1034
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
1035
     *
1036
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
1037
     */
1038
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
1039
    {
1040
        $course_id = api_get_course_int_id();
1041
        if (!empty($courseInfo)) {
1042
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
1043
        }
1044
1045
        // TODO: Implement a way of getting this to work when the current object is not set.
1046
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
1047
        // If an ID is specifically given and the current LP is not the same, prevent delete.
1048
        if (!empty($id) && ($id != $this->lp_id)) {
1049
            return false;
1050
        }
1051
1052
        $lp = Database::get_course_table(TABLE_LP_MAIN);
1053
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1054
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
1055
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1056
1057
        // Delete lp item id.
1058
        foreach ($this->items as $lpItemId => $dummy) {
1059
            $sql = "DELETE FROM $lp_item_view
1060
                    WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
1061
            Database::query($sql);
1062
        }
1063
1064
        // Proposed by Christophe (nickname: clefevre)
1065
        $sql = "DELETE FROM $lp_item
1066
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1067
        Database::query($sql);
1068
1069
        $sql = "DELETE FROM $lp_view
1070
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1071
        Database::query($sql);
1072
1073
        self::toggle_publish($this->lp_id, 'i');
1074
1075
        if ($this->type == 2 || $this->type == 3) {
1076
            // This is a scorm learning path, delete the files as well.
1077
            $sql = "SELECT path FROM $lp
1078
                    WHERE iid = ".$this->lp_id;
1079
            $res = Database::query($sql);
1080
            if (Database::num_rows($res) > 0) {
1081
                $row = Database::fetch_array($res);
1082
                $path = $row['path'];
1083
                $sql = "SELECT id FROM $lp
1084
                        WHERE
1085
                            c_id = $course_id AND
1086
                            path = '$path' AND
1087
                            iid != ".$this->lp_id;
1088
                $res = Database::query($sql);
1089
                if (Database::num_rows($res) > 0) {
1090
                    // Another learning path uses this directory, so don't delete it.
1091
                    if ($this->debug > 2) {
1092
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1093
                    }
1094
                } else {
1095
                    // No other LP uses that directory, delete it.
1096
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1097
                    // The absolute system path for this course.
1098
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
1099
                    if ($delete == 'remove' && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1100
                        if ($this->debug > 2) {
1101
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1102
                        }
1103
                        // Proposed by Christophe (clefevre).
1104
                        if (strcmp(substr($path, -2), "/.") == 0) {
1105
                            $path = substr($path, 0, -1); // Remove "." at the end.
1106
                        }
1107
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1108
                        rmdirr($course_scorm_dir.$path);
1109
                    }
1110
                }
1111
            }
1112
        }
1113
1114
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1115
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1116
        // Delete tools
1117
        $sql = "DELETE FROM $tbl_tool
1118
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1119
        Database::query($sql);
1120
1121
        if (api_get_configuration_value('allow_lp_subscription_to_usergroups')) {
1122
            $table = Database::get_course_table(TABLE_LP_REL_USERGROUP);
1123
            $sql = "DELETE FROM $table
1124
                    WHERE
1125
                        lp_id = {$this->lp_id} AND
1126
                        c_id = $course_id ";
1127
            Database::query($sql);
1128
        }
1129
1130
        $sql = "DELETE FROM $lp
1131
                WHERE iid = ".$this->lp_id;
1132
        Database::query($sql);
1133
        // Updates the display order of all lps.
1134
        $this->update_display_order();
1135
1136
        api_item_property_update(
1137
            api_get_course_info(),
1138
            TOOL_LEARNPATH,
1139
            $this->lp_id,
1140
            'delete',
1141
            api_get_user_id()
1142
        );
1143
1144
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1145
            api_get_course_id(),
1146
            4,
1147
            $id,
1148
            api_get_session_id()
1149
        );
1150
1151
        if ($link_info !== false) {
1152
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1153
        }
1154
1155
        if (api_get_setting('search_enabled') == 'true') {
1156
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1157
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1158
        }
1159
    }
1160
1161
    /**
1162
     * Removes all the children of one item - dangerous!
1163
     *
1164
     * @param int $id Element ID of which children have to be removed
1165
     *
1166
     * @return int Total number of children removed
1167
     */
1168
    public function delete_children_items($id)
1169
    {
1170
        $course_id = $this->course_info['real_id'];
1171
1172
        $num = 0;
1173
        $id = (int) $id;
1174
        if (empty($id) || empty($course_id)) {
1175
            return false;
1176
        }
1177
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1178
        $sql = "SELECT * FROM $lp_item
1179
                WHERE c_id = $course_id AND parent_item_id = $id";
1180
        $res = Database::query($sql);
1181
        while ($row = Database::fetch_array($res)) {
1182
            $num += $this->delete_children_items($row['iid']);
1183
            $sql = "DELETE FROM $lp_item
1184
                    WHERE c_id = $course_id AND iid = ".$row['iid'];
1185
            Database::query($sql);
1186
            $num++;
1187
        }
1188
1189
        return $num;
1190
    }
1191
1192
    /**
1193
     * Removes an item from the current learnpath.
1194
     *
1195
     * @param int $id Elem ID (0 if first)
1196
     *
1197
     * @return int Number of elements moved
1198
     *
1199
     * @todo implement resource removal
1200
     */
1201
    public function delete_item($id)
1202
    {
1203
        $course_id = api_get_course_int_id();
1204
        $id = (int) $id;
1205
        // TODO: Implement the resource removal.
1206
        if (empty($id) || empty($course_id)) {
1207
            return false;
1208
        }
1209
        // First select item to get previous, next, and display order.
1210
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1211
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1212
        $res_sel = Database::query($sql_sel);
1213
        if (Database::num_rows($res_sel) < 1) {
1214
            return false;
1215
        }
1216
        $row = Database::fetch_array($res_sel);
1217
        $previous = $row['previous_item_id'];
1218
        $next = $row['next_item_id'];
1219
        $display = $row['display_order'];
1220
        $parent = $row['parent_item_id'];
1221
        $lp = $row['lp_id'];
1222
        // Delete children items.
1223
        $this->delete_children_items($id);
1224
        // Now delete the item.
1225
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1226
        Database::query($sql_del);
1227
        // Now update surrounding items.
1228
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1229
                    WHERE iid = $previous";
1230
        Database::query($sql_upd);
1231
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1232
                    WHERE iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
1233
        Database::query($sql_upd);
1234
        // Now update all following items with new display order.
1235
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1236
                    WHERE
1237
                        c_id = $course_id AND
1238
                        lp_id = $lp AND
1239
                        parent_item_id = $parent AND
1240
                        display_order > $display";
1241
        Database::query($sql_all);
1242
1243
        //Removing prerequisites since the item will not longer exist
1244
        $sql_all = "UPDATE $lp_item SET prerequisite = ''
1245
                    WHERE c_id = $course_id AND prerequisite = $id";
1246
        Database::query($sql_all);
1247
1248
        $sql = "UPDATE $lp_item
1249
                SET previous_item_id = ".$this->getLastInFirstLevel()."
1250
                WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
1251
        Database::query($sql);
1252
1253
        // Remove from search engine if enabled.
1254
        if (api_get_setting('search_enabled') === 'true') {
1255
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1256
            $sql = 'SELECT * FROM %s
1257
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1258
                    LIMIT 1';
1259
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1260
            $res = Database::query($sql);
1261
            if (Database::num_rows($res) > 0) {
1262
                $row2 = Database::fetch_array($res);
1263
                $di = new ChamiloIndexer();
1264
                $di->remove_document($row2['search_did']);
1265
            }
1266
            $sql = 'DELETE FROM %s
1267
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1268
                    LIMIT 1';
1269
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1270
            Database::query($sql);
1271
        }
1272
    }
1273
1274
    /**
1275
     * Updates an item's content in place.
1276
     *
1277
     * @param int    $id               Element ID
1278
     * @param int    $parent           Parent item ID
1279
     * @param int    $previous         Previous item ID
1280
     * @param string $title            Item title
1281
     * @param string $description      Item description
1282
     * @param string $prerequisites    Prerequisites (optional)
1283
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1284
     * @param int    $max_time_allowed
1285
     * @param string $url
1286
     *
1287
     * @return bool True on success, false on error
1288
     */
1289
    public function edit_item(
1290
        $id,
1291
        $parent,
1292
        $previous,
1293
        $title,
1294
        $description,
1295
        $prerequisites = '0',
1296
        $audio = [],
1297
        $max_time_allowed = 0,
1298
        $url = ''
1299
    ) {
1300
        $course_id = api_get_course_int_id();
1301
        $_course = api_get_course_info();
1302
        $id = (int) $id;
1303
1304
        if (empty($max_time_allowed)) {
1305
            $max_time_allowed = 0;
1306
        }
1307
1308
        if (empty($id) || empty($_course)) {
1309
            return false;
1310
        }
1311
1312
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1313
        $sql = "SELECT * FROM $tbl_lp_item
1314
                WHERE iid = $id";
1315
        $res_select = Database::query($sql);
1316
        $row_select = Database::fetch_array($res_select);
1317
        $audio_update_sql = '';
1318
        if (is_array($audio) && !empty($audio['tmp_name']) && $audio['error'] === 0) {
1319
            // Create the audio folder if it does not exist yet.
1320
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1321
            if (!is_dir($filepath.'audio')) {
1322
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1323
                $audio_id = add_document(
1324
                    $_course,
1325
                    '/audio',
1326
                    'folder',
1327
                    0,
1328
                    'audio'
1329
                );
1330
                api_item_property_update(
1331
                    $_course,
1332
                    TOOL_DOCUMENT,
1333
                    $audio_id,
1334
                    'FolderCreated',
1335
                    api_get_user_id(),
1336
                    null,
1337
                    null,
1338
                    null,
1339
                    null,
1340
                    api_get_session_id()
1341
                );
1342
                api_item_property_update(
1343
                    $_course,
1344
                    TOOL_DOCUMENT,
1345
                    $audio_id,
1346
                    'invisible',
1347
                    api_get_user_id(),
1348
                    null,
1349
                    null,
1350
                    null,
1351
                    null,
1352
                    api_get_session_id()
1353
                );
1354
            }
1355
1356
            // Upload file in documents.
1357
            $pi = pathinfo($audio['name']);
1358
            if ($pi['extension'] === 'mp3') {
1359
                $c_det = api_get_course_info($this->cc);
1360
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1361
                $path = handle_uploaded_document(
1362
                    $c_det,
1363
                    $audio,
1364
                    $bp,
1365
                    '/audio',
1366
                    api_get_user_id(),
1367
                    0,
1368
                    null,
1369
                    0,
1370
                    'rename',
1371
                    false,
1372
                    0
1373
                );
1374
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1375
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1376
            }
1377
        }
1378
1379
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1380
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1381
1382
        // TODO: htmlspecialchars to be checked for encoding related problems.
1383
        if ($same_parent && $same_previous) {
1384
            // Only update title and description.
1385
            $sql = "UPDATE $tbl_lp_item
1386
                    SET title = '".Database::escape_string($title)."',
1387
                        prerequisite = '".$prerequisites."',
1388
                        description = '".Database::escape_string($description)."'
1389
                        ".$audio_update_sql.",
1390
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1391
                    WHERE iid = $id";
1392
            Database::query($sql);
1393
        } else {
1394
            $old_parent = $row_select['parent_item_id'];
1395
            $old_previous = $row_select['previous_item_id'];
1396
            $old_next = $row_select['next_item_id'];
1397
            $old_order = $row_select['display_order'];
1398
            $old_prerequisite = $row_select['prerequisite'];
1399
            $old_max_time_allowed = $row_select['max_time_allowed'];
1400
1401
            /* BEGIN -- virtually remove the current item id */
1402
            /* for the next and previous item it is like the current item doesn't exist anymore */
1403
            if ($old_previous != 0) {
1404
                // Next
1405
                $sql = "UPDATE $tbl_lp_item
1406
                        SET next_item_id = $old_next
1407
                        WHERE iid = $old_previous";
1408
                Database::query($sql);
1409
            }
1410
1411
            if (!empty($old_next)) {
1412
                // Previous
1413
                $sql = "UPDATE $tbl_lp_item
1414
                        SET previous_item_id = $old_previous
1415
                        WHERE iid = $old_next";
1416
                Database::query($sql);
1417
            }
1418
1419
            // display_order - 1 for every item with a display_order
1420
            // bigger then the display_order of the current item.
1421
            $sql = "UPDATE $tbl_lp_item
1422
                    SET display_order = display_order - 1
1423
                    WHERE
1424
                        c_id = $course_id AND
1425
                        display_order > $old_order AND
1426
                        lp_id = ".$this->lp_id." AND
1427
                        parent_item_id = $old_parent";
1428
            Database::query($sql);
1429
            /* END -- virtually remove the current item id */
1430
1431
            /* BEGIN -- update the current item id to his new location */
1432
            if ($previous == 0) {
1433
                // Select the data of the item that should come after the current item.
1434
                $sql = "SELECT id, display_order
1435
                        FROM $tbl_lp_item
1436
                        WHERE
1437
                            c_id = $course_id AND
1438
                            lp_id = ".$this->lp_id." AND
1439
                            parent_item_id = $parent AND
1440
                            previous_item_id = $previous";
1441
                $res_select_old = Database::query($sql);
1442
                $row_select_old = Database::fetch_array($res_select_old);
1443
1444
                // If the new parent didn't have children before.
1445
                if (Database::num_rows($res_select_old) == 0) {
1446
                    $new_next = 0;
1447
                    $new_order = 1;
1448
                } else {
1449
                    $new_next = $row_select_old['id'];
1450
                    $new_order = $row_select_old['display_order'];
1451
                }
1452
            } else {
1453
                // Select the data of the item that should come before the current item.
1454
                $sql = "SELECT next_item_id, display_order
1455
                        FROM $tbl_lp_item
1456
                        WHERE iid = $previous";
1457
                $res_select_old = Database::query($sql);
1458
                $row_select_old = Database::fetch_array($res_select_old);
1459
                $new_next = $row_select_old['next_item_id'];
1460
                $new_order = $row_select_old['display_order'] + 1;
1461
            }
1462
1463
            // TODO: htmlspecialchars to be checked for encoding related problems.
1464
            // Update the current item with the new data.
1465
            $sql = "UPDATE $tbl_lp_item
1466
                    SET
1467
                        title = '".Database::escape_string($title)."',
1468
                        description = '".Database::escape_string($description)."',
1469
                        parent_item_id = $parent,
1470
                        previous_item_id = $previous,
1471
                        next_item_id = $new_next,
1472
                        display_order = $new_order
1473
                        $audio_update_sql
1474
                    WHERE iid = $id";
1475
            Database::query($sql);
1476
1477
            if ($previous != 0) {
1478
                // Update the previous item's next_item_id.
1479
                $sql = "UPDATE $tbl_lp_item
1480
                        SET next_item_id = $id
1481
                        WHERE iid = $previous";
1482
                Database::query($sql);
1483
            }
1484
1485
            if (!empty($new_next)) {
1486
                // Update the next item's previous_item_id.
1487
                $sql = "UPDATE $tbl_lp_item
1488
                        SET previous_item_id = $id
1489
                        WHERE iid = $new_next";
1490
                Database::query($sql);
1491
            }
1492
1493
            if ($old_prerequisite != $prerequisites) {
1494
                $sql = "UPDATE $tbl_lp_item
1495
                        SET prerequisite = '$prerequisites'
1496
                        WHERE iid = $id";
1497
                Database::query($sql);
1498
            }
1499
1500
            if ($old_max_time_allowed != $max_time_allowed) {
1501
                // update max time allowed
1502
                $sql = "UPDATE $tbl_lp_item
1503
                        SET max_time_allowed = $max_time_allowed
1504
                        WHERE iid = $id";
1505
                Database::query($sql);
1506
            }
1507
1508
            // Update all the items with the same or a bigger display_order than the current item.
1509
            $sql = "UPDATE $tbl_lp_item
1510
                    SET display_order = display_order + 1
1511
                    WHERE
1512
                       c_id = $course_id AND
1513
                       lp_id = ".$this->get_id()." AND
1514
                       iid <> $id AND
1515
                       parent_item_id = $parent AND
1516
                       display_order >= $new_order";
1517
            Database::query($sql);
1518
        }
1519
1520
        if ($row_select['item_type'] == 'link') {
1521
            $link = new Link();
1522
            $linkId = $row_select['path'];
1523
            $link->updateLink($linkId, $url);
1524
        }
1525
    }
1526
1527
    /**
1528
     * Updates an item's prereq in place.
1529
     *
1530
     * @param int    $id              Element ID
1531
     * @param string $prerequisite_id Prerequisite Element ID
1532
     * @param int    $minScore        Prerequisite min score
1533
     * @param int    $maxScore        Prerequisite max score
1534
     *
1535
     * @return bool True on success, false on error
1536
     */
1537
    public function edit_item_prereq(
1538
        $id,
1539
        $prerequisite_id,
1540
        $minScore = 0,
1541
        $maxScore = 100
1542
    ) {
1543
        $id = (int) $id;
1544
1545
        if (empty($id)) {
1546
            return false;
1547
        }
1548
1549
        $prerequisite_id = (int) $prerequisite_id;
1550
1551
        if (empty($minScore) || $minScore < 0) {
1552
            $minScore = 0;
1553
        }
1554
1555
        if (empty($maxScore) || $maxScore < 0) {
1556
            $maxScore = 100;
1557
        }
1558
1559
        $minScore = (float) $minScore;
1560
        $maxScore = (float) $maxScore;
1561
1562
        if (empty($prerequisite_id)) {
1563
            $prerequisite_id = 'NULL';
1564
            $minScore = 0;
1565
            $maxScore = 100;
1566
        }
1567
1568
        $table = Database::get_course_table(TABLE_LP_ITEM);
1569
        $sql = " UPDATE $table
1570
                 SET
1571
                    prerequisite = $prerequisite_id ,
1572
                    prerequisite_min_score = $minScore ,
1573
                    prerequisite_max_score = $maxScore
1574
                 WHERE iid = $id";
1575
        Database::query($sql);
1576
1577
        return true;
1578
    }
1579
1580
    /**
1581
     * Get the specific prefix index terms of this learning path.
1582
     *
1583
     * @param string $prefix
1584
     *
1585
     * @return array Array of terms
1586
     */
1587
    public function get_common_index_terms_by_prefix($prefix)
1588
    {
1589
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1590
        $terms = get_specific_field_values_list_by_prefix(
1591
            $prefix,
1592
            $this->cc,
1593
            TOOL_LEARNPATH,
1594
            $this->lp_id
1595
        );
1596
        $prefix_terms = [];
1597
        if (!empty($terms)) {
1598
            foreach ($terms as $term) {
1599
                $prefix_terms[] = $term['value'];
1600
            }
1601
        }
1602
1603
        return $prefix_terms;
1604
    }
1605
1606
    /**
1607
     * Gets the number of items currently completed.
1608
     *
1609
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1610
     *
1611
     * @return int The number of items currently completed
1612
     */
1613
    public function get_complete_items_count($failedStatusException = false, $typeListNotToCount = [])
1614
    {
1615
        $i = 0;
1616
        $completedStatusList = [
1617
            'completed',
1618
            'passed',
1619
            'succeeded',
1620
            'browsed',
1621
        ];
1622
1623
        if (!$failedStatusException) {
1624
            $completedStatusList[] = 'failed';
1625
        }
1626
1627
        if ($this->debug) {
1628
            error_log('START - get_complete_items_count');
1629
            error_log('Counting steps with status in: '.print_r($completedStatusList, 1));
1630
        }
1631
1632
        $chapters = self::getChapterTypes();
1633
        if (!empty($typeListNotToCount)) {
1634
            $typeListNotToCount = array_merge($typeListNotToCount, $chapters);
1635
        } else {
1636
            $typeListNotToCount = $chapters;
1637
        }
1638
1639
        foreach ($this->items as $id => $dummy) {
1640
            // Trying failed and browsed considered "progressed" as well.
1641
            if ($this->items[$id]->status_is($completedStatusList) &&
1642
                !in_array($this->items[$id]->get_type(), $typeListNotToCount)
1643
            ) {
1644
                $i++;
1645
            }
1646
        }
1647
1648
        if ($this->debug) {
1649
            error_log('END - Count: '.$i);
1650
        }
1651
1652
        return $i;
1653
    }
1654
1655
    /**
1656
     * Gets the current item ID.
1657
     *
1658
     * @return int The current learnpath item id
1659
     */
1660
    public function get_current_item_id()
1661
    {
1662
        $current = 0;
1663
        if (!empty($this->current)) {
1664
            $current = (int) $this->current;
1665
        }
1666
1667
        return $current;
1668
    }
1669
1670
    /**
1671
     * Force to get the first learnpath item id.
1672
     *
1673
     * @return int The current learnpath item id
1674
     */
1675
    public function get_first_item_id()
1676
    {
1677
        $current = 0;
1678
        if (is_array($this->ordered_items)) {
1679
            $current = $this->ordered_items[0];
1680
        }
1681
1682
        return $current;
1683
    }
1684
1685
    /**
1686
     * Gets the total number of items available for viewing in this SCORM.
1687
     *
1688
     * @return int The total number of items
1689
     */
1690
    public function get_total_items_count()
1691
    {
1692
        return count($this->items);
1693
    }
1694
1695
    /**
1696
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1697
     *
1698
     * @return int The total no-chapters number of items
1699
     */
1700
    public function getTotalItemsCountWithoutDirs($typeListNotToCount = [])
1701
    {
1702
        $total = 0;
1703
        $chapters = self::getChapterTypes();
1704
        if (!empty($typeListNotToCount)) {
1705
            $typeListNotToCount = array_merge($typeListNotToCount, $chapters);
1706
        } else {
1707
            $typeListNotToCount = $chapters;
1708
        }
1709
        foreach ($this->items as $temp2) {
1710
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1711
                $total++;
1712
            }
1713
        }
1714
1715
        return $total;
1716
    }
1717
1718
    /**
1719
     *  Sets the first element URL.
1720
     */
1721
    public function first()
1722
    {
1723
        if ($this->debug > 0) {
1724
            error_log('In learnpath::first()', 0);
1725
            error_log('$this->last_item_seen '.$this->last_item_seen);
1726
        }
1727
1728
        // Test if the last_item_seen exists and is not a dir.
1729
        if (count($this->ordered_items) == 0) {
1730
            $this->index = 0;
1731
        }
1732
1733
        if (!empty($this->last_item_seen) &&
1734
            !empty($this->items[$this->last_item_seen]) &&
1735
            $this->items[$this->last_item_seen]->get_type() != 'dir'
1736
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1737
            //&& !$this->items[$this->last_item_seen]->is_done()
1738
        ) {
1739
            if ($this->debug > 2) {
1740
                error_log(
1741
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1742
                    $this->items[$this->last_item_seen]->get_type()
1743
                );
1744
            }
1745
            $index = -1;
1746
            foreach ($this->ordered_items as $myindex => $item_id) {
1747
                if ($item_id == $this->last_item_seen) {
1748
                    $index = $myindex;
1749
                    break;
1750
                }
1751
            }
1752
            if ($index == -1) {
1753
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1754
                if ($this->debug > 2) {
1755
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1756
                }
1757
1758
                return false;
1759
            } else {
1760
                $this->last = $this->last_item_seen;
1761
                $this->current = $this->last_item_seen;
1762
                $this->index = $index;
1763
            }
1764
        } else {
1765
            if ($this->debug > 2) {
1766
                error_log('In learnpath::first() - No last item seen', 0);
1767
            }
1768
            $index = 0;
1769
            // Loop through all ordered items and stop at the first item that is
1770
            // not a directory *and* that has not been completed yet.
1771
            while (!empty($this->ordered_items[$index]) &&
1772
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1773
                (
1774
                    $this->items[$this->ordered_items[$index]]->get_type() == 'dir' ||
1775
                    $this->items[$this->ordered_items[$index]]->is_done() === true
1776
                ) && $index < $this->max_ordered_items) {
1777
                $index++;
1778
            }
1779
1780
            $this->last = $this->current;
1781
            // current is
1782
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1783
            $this->index = $index;
1784
            if ($this->debug > 2) {
1785
                error_log('$index '.$index);
1786
                error_log('In learnpath::first() - No last item seen');
1787
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1788
            }
1789
        }
1790
        if ($this->debug > 2) {
1791
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1792
        }
1793
    }
1794
1795
    /**
1796
     * Gets the js library from the database.
1797
     *
1798
     * @return string The name of the javascript library to be used
1799
     */
1800
    public function get_js_lib()
1801
    {
1802
        $lib = '';
1803
        if (!empty($this->js_lib)) {
1804
            $lib = $this->js_lib;
1805
        }
1806
1807
        return $lib;
1808
    }
1809
1810
    /**
1811
     * Gets the learnpath database ID.
1812
     *
1813
     * @return int Learnpath ID in the lp table
1814
     */
1815
    public function get_id()
1816
    {
1817
        if (!empty($this->lp_id)) {
1818
            return (int) $this->lp_id;
1819
        }
1820
1821
        return 0;
1822
    }
1823
1824
    /**
1825
     * Gets the last element URL.
1826
     *
1827
     * @return string URL to load into the viewer
1828
     */
1829
    public function get_last()
1830
    {
1831
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1832
        if (count($this->ordered_items) > 0) {
1833
            $this->index = count($this->ordered_items) - 1;
1834
1835
            return $this->ordered_items[$this->index];
1836
        }
1837
1838
        return false;
1839
    }
1840
1841
    /**
1842
     * Get the last element in the first level.
1843
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1844
     *
1845
     * @return mixed
1846
     */
1847
    public function getLastInFirstLevel()
1848
    {
1849
        try {
1850
            $lastId = Database::getManager()
1851
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1852
                WHERE i.lpId = :lp AND i.parentItemId = 0 AND i.itemType != :type ORDER BY i.displayOrder DESC')
1853
                ->setMaxResults(1)
1854
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1855
                ->getSingleScalarResult();
1856
1857
            return $lastId;
1858
        } catch (Exception $exception) {
1859
            return 0;
1860
        }
1861
    }
1862
1863
    /**
1864
     * Get the learning path name by id.
1865
     *
1866
     * @param int $lpId
1867
     *
1868
     * @return mixed
1869
     */
1870
    public static function getLpNameById($lpId)
1871
    {
1872
        $em = Database::getManager();
1873
1874
        return $em->createQuery('SELECT clp.name FROM ChamiloCourseBundle:CLp clp
1875
            WHERE clp.iid = :iid')
1876
            ->setParameter('iid', $lpId)
1877
            ->getSingleScalarResult();
1878
    }
1879
1880
    /**
1881
     * Gets the navigation bar for the learnpath display screen.
1882
     *
1883
     * @param string $barId
1884
     *
1885
     * @return string The HTML string to use as a navigation bar
1886
     */
1887
    public function get_navigation_bar($barId = '')
1888
    {
1889
        if (empty($barId)) {
1890
            $barId = 'control-top';
1891
        }
1892
        $lpId = $this->lp_id;
1893
        $mycurrentitemid = $this->get_current_item_id();
1894
1895
        $reportingText = get_lang('Reporting');
1896
        $previousText = get_lang('ScormPrevious');
1897
        $nextText = get_lang('ScormNext');
1898
        $fullScreenText = get_lang('ScormExitFullScreen');
1899
1900
        $settings = api_get_configuration_value('lp_view_settings');
1901
        $display = isset($settings['display']) ? $settings['display'] : false;
1902
        $reportingIcon = '
1903
            <a class="icon-toolbar"
1904
                id="stats_link"
1905
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
1906
                onclick="window.parent.API.save_asset(); return true;"
1907
                target="content_name" title="'.$reportingText.'">
1908
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1909
            </a>';
1910
1911
        if (!empty($display)) {
1912
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1913
            if ($showReporting === false) {
1914
                $reportingIcon = '';
1915
            }
1916
        }
1917
1918
        $hideArrows = false;
1919
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1920
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1921
        }
1922
1923
        $previousIcon = '';
1924
        $nextIcon = '';
1925
        if ($hideArrows === false) {
1926
            $previousIcon = '
1927
                <a class="icon-toolbar" id="scorm-previous" href="#"
1928
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1929
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1930
                </a>';
1931
1932
            $nextIcon = '
1933
                <a class="icon-toolbar" id="scorm-next" href="#"
1934
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1935
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1936
                </a>';
1937
        }
1938
1939
        if ($this->mode === 'fullscreen') {
1940
            $navbar = '
1941
                  <span id="'.$barId.'" class="buttons">
1942
                    '.$reportingIcon.'
1943
                    '.$previousIcon.'
1944
                    '.$nextIcon.'
1945
                    <a class="icon-toolbar" id="view-embedded"
1946
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1947
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1948
                    </a>
1949
                  </span>';
1950
        } else {
1951
            $navbar = '
1952
                 <span id="'.$barId.'" class="buttons text-right">
1953
                    '.$reportingIcon.'
1954
                    '.$previousIcon.'
1955
                    '.$nextIcon.'
1956
                </span>';
1957
        }
1958
1959
        return $navbar;
1960
    }
1961
1962
    /**
1963
     * Gets the next resource in queue (url).
1964
     *
1965
     * @return string URL to load into the viewer
1966
     */
1967
    public function get_next_index()
1968
    {
1969
        // TODO
1970
        $index = $this->index;
1971
        $index++;
1972
        while (
1973
            !empty($this->ordered_items[$index]) && ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') &&
1974
            $index < $this->max_ordered_items
1975
        ) {
1976
            $index++;
1977
            if ($index == $this->max_ordered_items) {
1978
                if ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') {
1979
                    return $this->index;
1980
                }
1981
1982
                return $index;
1983
            }
1984
        }
1985
        if (empty($this->ordered_items[$index])) {
1986
            return $this->index;
1987
        }
1988
1989
        return $index;
1990
    }
1991
1992
    /**
1993
     * Gets item_id for the next element.
1994
     *
1995
     * @return int Next item (DB) ID
1996
     */
1997
    public function get_next_item_id()
1998
    {
1999
        $new_index = $this->get_next_index();
2000
        if (!empty($new_index)) {
2001
            if (isset($this->ordered_items[$new_index])) {
2002
                return $this->ordered_items[$new_index];
2003
            }
2004
        }
2005
2006
        return 0;
2007
    }
2008
2009
    /**
2010
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
2011
     *
2012
     * Generally, the package provided is in the form of a zip file, so the function
2013
     * has been written to test a zip file. If not a zip, the function will return the
2014
     * default return value: ''
2015
     *
2016
     * @param string $file_path the path to the file
2017
     * @param string $file_name the original name of the file
2018
     *
2019
     * @return string 'scorm','aicc','scorm2004','dokeos', 'error-empty-package' if the package is empty, or '' if the package cannot be recognized
2020
     */
2021
    public static function getPackageType($file_path, $file_name)
2022
    {
2023
        // Get name of the zip file without the extension.
2024
        $file_info = pathinfo($file_name);
2025
        $extension = $file_info['extension']; // Extension only.
2026
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
2027
                'dll',
2028
                'exe',
2029
            ])) {
2030
            return 'oogie';
2031
        }
2032
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
2033
                'dll',
2034
                'exe',
2035
            ])) {
2036
            return 'woogie';
2037
        }
2038
2039
        $zipFile = new PclZip($file_path);
2040
        // Check the zip content (real size and file extension).
2041
        $zipContentArray = $zipFile->listContent();
2042
        $package_type = '';
2043
        $manifest = '';
2044
        $aicc_match_crs = 0;
2045
        $aicc_match_au = 0;
2046
        $aicc_match_des = 0;
2047
        $aicc_match_cst = 0;
2048
        $countItems = 0;
2049
2050
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
2051
        if (is_array($zipContentArray)) {
2052
            $countItems = count($zipContentArray);
2053
            if ($countItems > 0) {
2054
                foreach ($zipContentArray as $thisContent) {
2055
                    if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
2056
                        // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
2057
                    } elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) {
2058
                        $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
2059
                        $package_type = 'scorm';
2060
                        break; // Exit the foreach loop.
2061
                    } elseif (
2062
                        preg_match('/aicc\//i', $thisContent['filename']) ||
2063
                        in_array(
2064
                            strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
2065
                            ['crs', 'au', 'des', 'cst']
2066
                        )
2067
                    ) {
2068
                        $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
2069
                        switch ($ext) {
2070
                            case 'crs':
2071
                                $aicc_match_crs = 1;
2072
                                break;
2073
                            case 'au':
2074
                                $aicc_match_au = 1;
2075
                                break;
2076
                            case 'des':
2077
                                $aicc_match_des = 1;
2078
                                break;
2079
                            case 'cst':
2080
                                $aicc_match_cst = 1;
2081
                                break;
2082
                            default:
2083
                                break;
2084
                        }
2085
                        //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
2086
                    } else {
2087
                        $package_type = '';
2088
                    }
2089
                }
2090
            }
2091
        }
2092
2093
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
2094
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
2095
            $package_type = 'aicc';
2096
        }
2097
2098
        // Try with chamilo course builder
2099
        if (empty($package_type)) {
2100
            // Sometimes users will try to upload an empty zip, or a zip with
2101
            // only a folder. Catch that and make the calling function aware.
2102
            // If the single file was the imsmanifest.xml, then $package_type
2103
            // would be 'scorm' and we wouldn't be here.
2104
            if ($countItems < 2) {
2105
                return 'error-empty-package';
2106
            }
2107
            $package_type = 'chamilo';
2108
        }
2109
2110
        return $package_type;
2111
    }
2112
2113
    /**
2114
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
2115
     *
2116
     * @return string URL to load into the viewer
2117
     */
2118
    public function get_previous_index()
2119
    {
2120
        $index = $this->index;
2121
        if (isset($this->ordered_items[$index - 1])) {
2122
            $index--;
2123
            while (isset($this->ordered_items[$index]) &&
2124
                ($this->items[$this->ordered_items[$index]]->get_type() == 'dir')
2125
            ) {
2126
                $index--;
2127
                if ($index < 0) {
2128
                    return $this->index;
2129
                }
2130
            }
2131
        }
2132
2133
        return $index;
2134
    }
2135
2136
    /**
2137
     * Gets item_id for the next element.
2138
     *
2139
     * @return int Previous item (DB) ID
2140
     */
2141
    public function get_previous_item_id()
2142
    {
2143
        $index = $this->get_previous_index();
2144
2145
        return $this->ordered_items[$index];
2146
    }
2147
2148
    /**
2149
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2150
     *
2151
     * @param int    $lpItemId
2152
     * @param string $autostart
2153
     *
2154
     * @return string The mediaplayer HTML
2155
     */
2156
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2157
    {
2158
        $course_id = api_get_course_int_id();
2159
        $courseInfo = api_get_course_info();
2160
        $lpItemId = (int) $lpItemId;
2161
2162
        if (empty($courseInfo) || empty($lpItemId)) {
2163
            return '';
2164
        }
2165
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
2166
2167
        if (empty($item)) {
2168
            return '';
2169
        }
2170
2171
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2172
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2173
        $itemViewId = (int) $item->db_item_view_id;
2174
2175
        // Getting all the information about the item.
2176
        $sql = "SELECT lp_view.status
2177
                FROM $tbl_lp_item as lpi
2178
                INNER JOIN $tbl_lp_item_view as lp_view
2179
                ON (lpi.iid = lp_view.lp_item_id)
2180
                WHERE
2181
                    lp_view.iid = $itemViewId AND
2182
                    lpi.iid = $lpItemId AND
2183
                    lp_view.c_id = $course_id";
2184
        $result = Database::query($sql);
2185
        $row = Database::fetch_assoc($result);
2186
        $output = '';
2187
        $audio = $item->audio;
2188
2189
        if (!empty($audio)) {
2190
            $list = $_SESSION['oLP']->get_toc();
2191
2192
            switch ($item->get_type()) {
2193
                case 'quiz':
2194
                    $type_quiz = false;
2195
                    foreach ($list as $toc) {
2196
                        if ($toc['id'] == $_SESSION['oLP']->current) {
2197
                            $type_quiz = true;
2198
                        }
2199
                    }
2200
2201
                    if ($type_quiz) {
2202
                        if ($_SESSION['oLP']->prevent_reinit == 1) {
2203
                            $autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
2204
                        } else {
2205
                            $autostart_audio = $autostart;
2206
                        }
2207
                    }
2208
                    break;
2209
                case TOOL_READOUT_TEXT:
2210
                    $autostart_audio = 'false';
2211
                    break;
2212
                default:
2213
                    $autostart_audio = 'true';
2214
            }
2215
2216
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'.$audio;
2217
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document'.$audio.'?'.api_get_cidreq();
2218
2219
            $player = Display::getMediaPlayer(
2220
                $file,
2221
                [
2222
                    'id' => 'lp_audio_media_player',
2223
                    'url' => $url,
2224
                    'autoplay' => $autostart_audio,
2225
                    'width' => '100%',
2226
                ]
2227
            );
2228
2229
            // The mp3 player.
2230
            $output = '<div id="container">';
2231
            $output .= $player;
2232
            $output .= '</div>';
2233
        }
2234
2235
        return $output;
2236
    }
2237
2238
    /**
2239
     * @param int   $studentId
2240
     * @param int   $prerequisite
2241
     * @param array $courseInfo
2242
     * @param int   $sessionId
2243
     *
2244
     * @return bool
2245
     */
2246
    public static function isBlockedByPrerequisite(
2247
        $studentId,
2248
        $prerequisite,
2249
        $courseInfo,
2250
        $sessionId
2251
    ) {
2252
        if (empty($courseInfo)) {
2253
            return false;
2254
        }
2255
2256
        $courseId = $courseInfo['real_id'];
2257
2258
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2259
        if ($allow) {
2260
            if (api_is_allowed_to_edit() ||
2261
                api_is_platform_admin(true) ||
2262
                api_is_drh() ||
2263
                api_is_coach($sessionId, $courseId, false)
2264
            ) {
2265
                return false;
2266
            }
2267
        }
2268
2269
        $isBlocked = false;
2270
        if (!empty($prerequisite)) {
2271
            $progress = self::getProgress(
2272
                $prerequisite,
2273
                $studentId,
2274
                $courseId,
2275
                $sessionId
2276
            );
2277
            if ($progress < 100) {
2278
                $isBlocked = true;
2279
            }
2280
2281
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2282
                // Block if it does not exceed minimum time
2283
                // Minimum time (in minutes) to pass the learning path
2284
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
2285
2286
                if ($accumulateWorkTime > 0) {
2287
                    // Total time in course (sum of times in learning paths from course)
2288
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
2289
2290
                    // Connect with the plugin_licences_course_session table
2291
                    // which indicates what percentage of the time applies
2292
                    // Minimum connection percentage
2293
                    $perc = 100;
2294
                    // Time from the course
2295
                    $tc = $accumulateWorkTimeTotal;
2296
2297
                    // Percentage of the learning paths
2298
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
2299
                    // Minimum time for each learning path
2300
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
2301
2302
                    // Spent time (in seconds) so far in the learning path
2303
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
2304
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
2305
2306
                    if ($lpTime < ($accumulateWorkTime * 60)) {
2307
                        $isBlocked = true;
2308
                    }
2309
                }
2310
            }
2311
        }
2312
2313
        return $isBlocked;
2314
    }
2315
2316
    /**
2317
     * Checks if the learning path is visible for student after the progress
2318
     * of its prerequisite is completed, considering the time availability and
2319
     * the LP visibility.
2320
     *
2321
     * @param int   $lp_id
2322
     * @param int   $student_id
2323
     * @param array $courseInfo
2324
     * @param int   $sessionId
2325
     * @param bool  $checkSubscription Optional. Allow don't check if user is subscribed to the LP.
2326
     *
2327
     * @return bool
2328
     */
2329
    public static function is_lp_visible_for_student(
2330
        $lp_id,
2331
        $student_id,
2332
        $courseInfo = [],
2333
        $sessionId = 0,
2334
        bool $checkSubscription = true
2335
    ) {
2336
        $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
2337
        $lp_id = (int) $lp_id;
2338
        $sessionId = (int) $sessionId;
2339
2340
        if (empty($courseInfo)) {
2341
            return false;
2342
        }
2343
2344
        if (empty($sessionId)) {
2345
            $sessionId = api_get_session_id();
2346
        }
2347
2348
        $courseId = $courseInfo['real_id'];
2349
2350
        $itemInfo = api_get_item_property_info(
2351
            $courseId,
2352
            TOOL_LEARNPATH,
2353
            $lp_id,
2354
            $sessionId
2355
        );
2356
2357
        // If the item was deleted.
2358
        if (isset($itemInfo['visibility']) && $itemInfo['visibility'] == 2) {
2359
            return false;
2360
        }
2361
2362
        // @todo remove this query and load the row info as a parameter
2363
        $table = Database::get_course_table(TABLE_LP_MAIN);
2364
        // Get current prerequisite
2365
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on, category_id
2366
                FROM $table
2367
                WHERE iid = $lp_id";
2368
        $rs = Database::query($sql);
2369
        $now = time();
2370
        if (Database::num_rows($rs) > 0) {
2371
            $row = Database::fetch_array($rs, 'ASSOC');
2372
            if (!empty($row['category_id'])) {
2373
                $category = self::getCategory($row['category_id']);
2374
                if (self::categoryIsVisibleForStudent($category, api_get_user_entity($student_id)) === false) {
2375
                    return false;
2376
                }
2377
            }
2378
2379
            $prerequisite = $row['prerequisite'];
2380
            $is_visible = true;
2381
2382
            $isBlocked = self::isBlockedByPrerequisite(
2383
                $student_id,
2384
                $prerequisite,
2385
                $courseInfo,
2386
                $sessionId
2387
            );
2388
2389
            if ($isBlocked) {
2390
                $is_visible = false;
2391
            }
2392
2393
            // Also check the time availability of the LP
2394
            if ($is_visible) {
2395
                // Adding visibility restrictions
2396
                if (!empty($row['publicated_on'])) {
2397
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2398
                        $is_visible = false;
2399
                    }
2400
                }
2401
                // Blocking empty start times see BT#2800
2402
                global $_custom;
2403
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2404
                    $_custom['lps_hidden_when_no_start_date']
2405
                ) {
2406
                    if (empty($row['publicated_on'])) {
2407
                        $is_visible = false;
2408
                    }
2409
                }
2410
2411
                if (!empty($row['expired_on'])) {
2412
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2413
                        $is_visible = false;
2414
                    }
2415
                }
2416
            }
2417
2418
            if ($is_visible && $checkSubscription) {
2419
                $is_visible = self::isUserSubscribedToLp(
2420
                    $row,
2421
                    (int) $student_id,
2422
                    $courseInfo,
2423
                    (int) $sessionId
2424
                );
2425
            }
2426
2427
            return $is_visible;
2428
        }
2429
2430
        return false;
2431
    }
2432
2433
    public static function isUserSubscribedToLp(
2434
        array $lpInfo,
2435
        int $studentId,
2436
        array $courseInfo,
2437
        int $sessionId = 0
2438
    ): bool {
2439
        $subscriptionSettings = self::getSubscriptionSettings();
2440
2441
        // Check if the subscription users/group to a LP is ON
2442
        if (isset($lpInfo['subscribe_users']) && $lpInfo['subscribe_users'] == 1 &&
2443
            $subscriptionSettings['allow_add_users_to_lp'] === true
2444
        ) {
2445
            // Checking only the user visibility
2446
            $userVisibility = api_get_item_visibility(
2447
                $courseInfo,
2448
                'learnpath',
2449
                $lpInfo['id'],
2450
                $sessionId,
2451
                $studentId,
2452
                'LearnpathSubscription'
2453
            );
2454
2455
            if (1 == $userVisibility) {
2456
                return true;
2457
            }
2458
2459
            // Try group
2460
            $userGroups = GroupManager::getAllGroupPerUserSubscription($studentId, $courseInfo['real_id']);
2461
2462
            if (!empty($userGroups)) {
2463
                foreach ($userGroups as $groupInfo) {
2464
                    $userVisibility = api_get_item_visibility(
2465
                        $courseInfo,
2466
                        'learnpath',
2467
                        $lpInfo['id'],
2468
                        $sessionId,
2469
                        null,
2470
                        'LearnpathSubscription',
2471
                        $groupInfo['iid']
2472
                    );
2473
2474
                    if (1 == $userVisibility) {
2475
                        return true;
2476
                    }
2477
                }
2478
            }
2479
2480
            return false;
2481
        }
2482
2483
        return true;
2484
    }
2485
2486
    /**
2487
     * @param int $lpId
2488
     * @param int $userId
2489
     * @param int $courseId
2490
     * @param int $sessionId
2491
     *
2492
     * @return int
2493
     */
2494
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2495
    {
2496
        $lpId = (int) $lpId;
2497
        $userId = (int) $userId;
2498
        $courseId = (int) $courseId;
2499
        $sessionId = (int) $sessionId;
2500
2501
        $sessionCondition = api_get_session_condition($sessionId);
2502
        $table = Database::get_course_table(TABLE_LP_VIEW);
2503
        $sql = "SELECT progress FROM $table
2504
                WHERE
2505
                    c_id = $courseId AND
2506
                    lp_id = $lpId AND
2507
                    user_id = $userId $sessionCondition ";
2508
        $res = Database::query($sql);
2509
2510
        $progress = 0;
2511
        if (Database::num_rows($res) > 0) {
2512
            $row = Database::fetch_array($res);
2513
            $progress = (int) $row['progress'];
2514
        }
2515
2516
        return $progress;
2517
    }
2518
2519
    /**
2520
     * @param array $lpList
2521
     * @param int   $userId
2522
     * @param int   $courseId
2523
     * @param int   $sessionId
2524
     *
2525
     * @return array
2526
     */
2527
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2528
    {
2529
        $lpList = array_map('intval', $lpList);
2530
        if (empty($lpList)) {
2531
            return [];
2532
        }
2533
2534
        $lpList = implode("','", $lpList);
2535
2536
        $userId = (int) $userId;
2537
        $courseId = (int) $courseId;
2538
        $sessionId = (int) $sessionId;
2539
2540
        $sessionCondition = api_get_session_condition($sessionId);
2541
        $table = Database::get_course_table(TABLE_LP_VIEW);
2542
        $sql = "SELECT lp_id, progress FROM $table
2543
                WHERE
2544
                    c_id = $courseId AND
2545
                    lp_id IN ('".$lpList."') AND
2546
                    user_id = $userId $sessionCondition ";
2547
        $res = Database::query($sql);
2548
2549
        if (Database::num_rows($res) > 0) {
2550
            $list = [];
2551
            while ($row = Database::fetch_array($res)) {
2552
                $list[$row['lp_id']] = $row['progress'];
2553
            }
2554
2555
            return $list;
2556
        }
2557
2558
        return [];
2559
    }
2560
2561
    /**
2562
     * Displays a progress bar
2563
     * completed so far.
2564
     *
2565
     * @param int    $percentage Progress value to display
2566
     * @param string $text_add   Text to display near the progress value
2567
     *
2568
     * @return string HTML string containing the progress bar
2569
     */
2570
    public static function get_progress_bar($percentage = -1, $text_add = '')
2571
    {
2572
        $text = $percentage.$text_add;
2573
        $output = '<div class="progress">
2574
            <div id="progress_bar_value"
2575
                class="progress-bar progress-bar-success" role="progressbar"
2576
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2577
            '.$text.'
2578
            </div>
2579
        </div>';
2580
2581
        return $output;
2582
    }
2583
2584
    /**
2585
     * @param string $mode can be '%' or 'abs'
2586
     *                     otherwise this value will be used $this->progress_bar_mode
2587
     *
2588
     * @return string
2589
     */
2590
    public function getProgressBar($mode = null)
2591
    {
2592
        [$percentage, $text_add] = $this->get_progress_bar_text($mode);
2593
2594
        return self::get_progress_bar($percentage, $text_add);
2595
    }
2596
2597
    /**
2598
     * Gets the progress bar info to display inside the progress bar.
2599
     * Also used by scorm_api.php.
2600
     *
2601
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2602
     *                     we display a number of completed elements per total elements
2603
     * @param int    $add  Additional steps to fake as completed
2604
     *
2605
     * @return array Percentage or number and symbol (% or /xx)
2606
     */
2607
    public function get_progress_bar_text($mode = '', $add = 0)
2608
    {
2609
        if (empty($mode)) {
2610
            $mode = $this->progress_bar_mode;
2611
        }
2612
        $text = '';
2613
        $percentage = 0;
2614
        // If the option to use the score as progress is set for this learning
2615
        // path, then the rules are completely different: we assume only one
2616
        // item exists and the progress of the LP depends on the score
2617
        $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
2618
2619
        if ($scoreAsProgressSetting === true) {
2620
            $scoreAsProgress = $this->getUseScoreAsProgress();
2621
            if ($scoreAsProgress) {
2622
                // Get single item's score
2623
                $itemId = $this->get_current_item_id();
2624
                $item = $this->getItem($itemId);
2625
                $score = $item->get_score();
2626
                $maxScore = $item->get_max();
2627
                if ($mode = '%') {
2628
                    if (!empty($maxScore)) {
2629
                        $percentage = ((float) $score / (float) $maxScore) * 100;
2630
                    }
2631
                    $percentage = number_format($percentage, 0);
2632
                    $text = '%';
2633
                } else {
2634
                    $percentage = $score;
2635
                    $text = '/'.$maxScore;
2636
                }
2637
2638
                return [$percentage, $text];
2639
            }
2640
        }
2641
        // otherwise just continue the normal processing of progress
2642
        $total_items = $this->getTotalItemsCountWithoutDirs();
2643
        $completeItems = $this->get_complete_items_count();
2644
        if ($add != 0) {
2645
            $completeItems += $add;
2646
        }
2647
        if ($completeItems > $total_items) {
2648
            $completeItems = $total_items;
2649
        }
2650
2651
        if ($mode == '%') {
2652
            if ($total_items > 0) {
2653
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2654
            }
2655
            $percentage = number_format($percentage, 0);
2656
            $text = '%';
2657
        } elseif ($mode === 'abs') {
2658
            $percentage = $completeItems;
2659
            $text = '/'.$total_items;
2660
        }
2661
2662
        return [
2663
            $percentage,
2664
            $text,
2665
        ];
2666
    }
2667
2668
    /**
2669
     * Gets the progress bar mode.
2670
     *
2671
     * @return string The progress bar mode attribute
2672
     */
2673
    public function get_progress_bar_mode()
2674
    {
2675
        if (!empty($this->progress_bar_mode)) {
2676
            return $this->progress_bar_mode;
2677
        }
2678
2679
        return '%';
2680
    }
2681
2682
    /**
2683
     * Gets the learnpath theme (remote or local).
2684
     *
2685
     * @return string Learnpath theme
2686
     */
2687
    public function get_theme()
2688
    {
2689
        if (!empty($this->theme)) {
2690
            return $this->theme;
2691
        }
2692
2693
        return '';
2694
    }
2695
2696
    /**
2697
     * Gets the learnpath session id.
2698
     *
2699
     * @return int
2700
     */
2701
    public function get_lp_session_id()
2702
    {
2703
        if (!empty($this->lp_session_id)) {
2704
            return (int) $this->lp_session_id;
2705
        }
2706
2707
        return 0;
2708
    }
2709
2710
    /**
2711
     * Gets the learnpath image.
2712
     *
2713
     * @return string Web URL of the LP image
2714
     */
2715
    public function get_preview_image()
2716
    {
2717
        if (!empty($this->preview_image)) {
2718
            return $this->preview_image;
2719
        }
2720
2721
        return '';
2722
    }
2723
2724
    /**
2725
     * @param string $size
2726
     * @param string $path_type
2727
     *
2728
     * @return bool|string
2729
     */
2730
    public function get_preview_image_path($size = null, $path_type = 'web')
2731
    {
2732
        $preview_image = $this->get_preview_image();
2733
        if (isset($preview_image) && !empty($preview_image)) {
2734
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2735
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2736
2737
            if (isset($size)) {
2738
                $info = pathinfo($preview_image);
2739
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2740
2741
                if (file_exists($image_sys_path.$image_custom_size)) {
2742
                    if ($path_type == 'web') {
2743
                        return $image_path.$image_custom_size;
2744
                    } else {
2745
                        return $image_sys_path.$image_custom_size;
2746
                    }
2747
                }
2748
            } else {
2749
                if ($path_type == 'web') {
2750
                    return $image_path.$preview_image;
2751
                } else {
2752
                    return $image_sys_path.$preview_image;
2753
                }
2754
            }
2755
        }
2756
2757
        return false;
2758
    }
2759
2760
    /**
2761
     * Gets the learnpath author.
2762
     *
2763
     * @return string LP's author
2764
     */
2765
    public function get_author()
2766
    {
2767
        if (!empty($this->author)) {
2768
            return $this->author;
2769
        }
2770
2771
        return '';
2772
    }
2773
2774
    /**
2775
     * Gets hide table of contents.
2776
     *
2777
     * @return int
2778
     */
2779
    public function getHideTableOfContents()
2780
    {
2781
        return (int) $this->hide_toc_frame;
2782
    }
2783
2784
    /**
2785
     * Generate a new prerequisites string for a given item. If this item was a sco and
2786
     * its prerequisites were strings (instead of IDs), then transform those strings into
2787
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2788
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2789
     * same rule as the scormExport() method.
2790
     *
2791
     * @param int $item_id Item ID
2792
     *
2793
     * @return string Prerequisites string ready for the export as SCORM
2794
     */
2795
    public function get_scorm_prereq_string($item_id)
2796
    {
2797
        if ($this->debug > 0) {
2798
            error_log('In learnpath::get_scorm_prereq_string()');
2799
        }
2800
        if (!is_object($this->items[$item_id])) {
2801
            return false;
2802
        }
2803
        /** @var learnpathItem $oItem */
2804
        $oItem = $this->items[$item_id];
2805
        $prereq = $oItem->get_prereq_string();
2806
2807
        if (empty($prereq)) {
2808
            return '';
2809
        }
2810
        if (preg_match('/^\d+$/', $prereq) &&
2811
            isset($this->items[$prereq]) &&
2812
            is_object($this->items[$prereq])
2813
        ) {
2814
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2815
            // then simply return it (with the ITEM_ prefix).
2816
            //return 'ITEM_' . $prereq;
2817
            return $this->items[$prereq]->ref;
2818
        } else {
2819
            if (isset($this->refs_list[$prereq])) {
2820
                // It's a simple string item from which the ID can be found in the refs list,
2821
                // so we can transform it directly to an ID for export.
2822
                return $this->items[$this->refs_list[$prereq]]->ref;
2823
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2824
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2825
            } else {
2826
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2827
                // and replace them, one by one, by the internal IDs (chamilo db)
2828
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2829
                // by a space as well.
2830
                $find = [
2831
                    '&',
2832
                    '|',
2833
                    '~',
2834
                    '=',
2835
                    '<>',
2836
                    '{',
2837
                    '}',
2838
                    '*',
2839
                    '(',
2840
                    ')',
2841
                ];
2842
                $replace = [
2843
                    ' ',
2844
                    ' ',
2845
                    ' ',
2846
                    ' ',
2847
                    ' ',
2848
                    ' ',
2849
                    ' ',
2850
                    ' ',
2851
                    ' ',
2852
                    ' ',
2853
                ];
2854
                $prereq_mod = str_replace($find, $replace, $prereq);
2855
                $ids = explode(' ', $prereq_mod);
2856
                foreach ($ids as $id) {
2857
                    $id = trim($id);
2858
                    if (isset($this->refs_list[$id])) {
2859
                        $prereq = preg_replace(
2860
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2861
                            'ITEM_'.$this->refs_list[$id],
2862
                            $prereq
2863
                        );
2864
                    }
2865
                }
2866
2867
                return $prereq;
2868
            }
2869
        }
2870
    }
2871
2872
    /**
2873
     * Returns the XML DOM document's node.
2874
     *
2875
     * @param DOMNodeList $children Reference to a list of objects to search for the given ITEM_*
2876
     * @param string      $id       The identifier to look for
2877
     *
2878
     * @return mixed The reference to the element found with that identifier. False if not found
2879
     */
2880
    public function get_scorm_xml_node(DOMNodeList &$children, string $id, $nodeName = 'item', $attributeName = 'identifier')
2881
    {
2882
        for ($i = 0; $i < $children->length; $i++) {
2883
            $item_temp = $children->item($i);
2884
            if ($item_temp->nodeName == $nodeName) {
2885
                if ($item_temp instanceof DOMElement && $item_temp->getAttribute($attributeName) == $id) {
2886
                    return $item_temp;
2887
                }
2888
            }
2889
            $subchildren = $item_temp->childNodes;
2890
            if ($subchildren && $subchildren->length > 0) {
2891
                $val = $this->get_scorm_xml_node($subchildren, $id, $nodeName, $attributeName);
2892
                if (is_object($val)) {
2893
                    return $val;
2894
                }
2895
            }
2896
        }
2897
2898
        return false;
2899
    }
2900
2901
    /**
2902
     * Gets the status list for all LP's items.
2903
     *
2904
     * @return array Array of [index] => [item ID => current status]
2905
     */
2906
    public function get_items_status_list()
2907
    {
2908
        $list = [];
2909
        foreach ($this->ordered_items as $item_id) {
2910
            $list[] = [
2911
                $item_id => $this->items[$item_id]->get_status(),
2912
            ];
2913
        }
2914
2915
        return $list;
2916
    }
2917
2918
    /**
2919
     * Return the number of interactions for the given learnpath Item View ID.
2920
     * This method can be used as static.
2921
     *
2922
     * @param int $lp_iv_id  Item View ID
2923
     * @param int $course_id course id
2924
     *
2925
     * @return int
2926
     */
2927
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2928
    {
2929
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2930
        $lp_iv_id = (int) $lp_iv_id;
2931
        $course_id = (int) $course_id;
2932
2933
        $sql = "SELECT count(*) FROM $table
2934
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2935
        $res = Database::query($sql);
2936
        $num = 0;
2937
        if (Database::num_rows($res)) {
2938
            $row = Database::fetch_array($res);
2939
            $num = $row[0];
2940
        }
2941
2942
        return $num;
2943
    }
2944
2945
    /**
2946
     * Return the interactions as an array for the given lp_iv_id.
2947
     * This method can be used as static.
2948
     *
2949
     * @param int $lp_iv_id Learnpath Item View ID
2950
     *
2951
     * @return array
2952
     *
2953
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2954
     */
2955
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2956
    {
2957
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2958
        $list = [];
2959
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2960
        $lp_iv_id = (int) $lp_iv_id;
2961
2962
        if (empty($lp_iv_id) || empty($course_id)) {
2963
            return [];
2964
        }
2965
2966
        $sql = "SELECT * FROM $table
2967
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2968
                ORDER BY order_id ASC";
2969
        $res = Database::query($sql);
2970
        $num = Database::num_rows($res);
2971
        if ($num > 0) {
2972
            $list[] = [
2973
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2974
                'id' => api_htmlentities(get_lang('InteractionID'), ENT_QUOTES),
2975
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2976
                'time' => api_htmlentities(get_lang('TimeFinished'), ENT_QUOTES),
2977
                'correct_responses' => api_htmlentities(get_lang('CorrectAnswers'), ENT_QUOTES),
2978
                'student_response' => api_htmlentities(get_lang('StudentResponse'), ENT_QUOTES),
2979
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2980
                'latency' => api_htmlentities(get_lang('LatencyTimeSpent'), ENT_QUOTES),
2981
                'student_response_formatted' => '',
2982
            ];
2983
            while ($row = Database::fetch_array($res)) {
2984
                $studentResponseFormatted = urldecode($row['student_response']);
2985
                $content_student_response = explode('__|', $studentResponseFormatted);
2986
                if (count($content_student_response) > 0) {
2987
                    if (count($content_student_response) >= 3) {
2988
                        // Pop the element off the end of array.
2989
                        array_pop($content_student_response);
2990
                    }
2991
                    $studentResponseFormatted = implode(',', $content_student_response);
2992
                }
2993
2994
                $list[] = [
2995
                    'order_id' => $row['order_id'] + 1,
2996
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2997
                    'type' => $row['interaction_type'],
2998
                    'time' => $row['completion_time'],
2999
                    'correct_responses' => '', // Hide correct responses from students.
3000
                    'student_response' => $row['student_response'],
3001
                    'result' => $row['result'],
3002
                    'latency' => $row['latency'],
3003
                    'student_response_formatted' => $studentResponseFormatted,
3004
                ];
3005
            }
3006
        }
3007
3008
        return $list;
3009
    }
3010
3011
    /**
3012
     * Return the number of objectives for the given learnpath Item View ID.
3013
     * This method can be used as static.
3014
     *
3015
     * @param int $lp_iv_id  Item View ID
3016
     * @param int $course_id Course ID
3017
     *
3018
     * @return int Number of objectives
3019
     */
3020
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
3021
    {
3022
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3023
        $course_id = (int) $course_id;
3024
        $lp_iv_id = (int) $lp_iv_id;
3025
        $sql = "SELECT count(*) FROM $table
3026
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
3027
        //@todo seems that this always returns 0
3028
        $res = Database::query($sql);
3029
        $num = 0;
3030
        if (Database::num_rows($res)) {
3031
            $row = Database::fetch_array($res);
3032
            $num = $row[0];
3033
        }
3034
3035
        return $num;
3036
    }
3037
3038
    /**
3039
     * Return the objectives as an array for the given lp_iv_id.
3040
     * This method can be used as static.
3041
     *
3042
     * @param int $lpItemViewId Learnpath Item View ID
3043
     * @param int $course_id
3044
     *
3045
     * @return array
3046
     *
3047
     * @todo    Translate labels
3048
     */
3049
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
3050
    {
3051
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
3052
        $lpItemViewId = (int) $lpItemViewId;
3053
3054
        if (empty($course_id) || empty($lpItemViewId)) {
3055
            return [];
3056
        }
3057
3058
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3059
        $sql = "SELECT * FROM $table
3060
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
3061
                ORDER BY order_id ASC";
3062
        $res = Database::query($sql);
3063
        $num = Database::num_rows($res);
3064
        $list = [];
3065
        if ($num > 0) {
3066
            $list[] = [
3067
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
3068
                'objective_id' => api_htmlentities(get_lang('ObjectiveID'), ENT_QUOTES),
3069
                'score_raw' => api_htmlentities(get_lang('ObjectiveRawScore'), ENT_QUOTES),
3070
                'score_max' => api_htmlentities(get_lang('ObjectiveMaxScore'), ENT_QUOTES),
3071
                'score_min' => api_htmlentities(get_lang('ObjectiveMinScore'), ENT_QUOTES),
3072
                'status' => api_htmlentities(get_lang('ObjectiveStatus'), ENT_QUOTES),
3073
            ];
3074
            while ($row = Database::fetch_array($res)) {
3075
                $list[] = [
3076
                    'order_id' => $row['order_id'] + 1,
3077
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
3078
                    'score_raw' => $row['score_raw'],
3079
                    'score_max' => $row['score_max'],
3080
                    'score_min' => $row['score_min'],
3081
                    'status' => $row['status'],
3082
                ];
3083
            }
3084
        }
3085
3086
        return $list;
3087
    }
3088
3089
    /**
3090
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
3091
     * used by get_html_toc() to be ready to display.
3092
     *
3093
     * @return array TOC as a table with 4 elements per row: title, link, status and level
3094
     */
3095
    public function get_toc()
3096
    {
3097
        $toc = [];
3098
        foreach ($this->ordered_items as $item_id) {
3099
            // TODO: Change this link generation and use new function instead.
3100
            $toc[] = [
3101
                'id' => $item_id,
3102
                'title' => $this->items[$item_id]->get_title(),
3103
                'status' => $this->items[$item_id]->get_status(),
3104
                'level' => $this->items[$item_id]->get_level(),
3105
                'type' => $this->items[$item_id]->get_type(),
3106
                'description' => $this->items[$item_id]->get_description(),
3107
                'path' => $this->items[$item_id]->get_path(),
3108
                'parent' => $this->items[$item_id]->get_parent(),
3109
            ];
3110
        }
3111
3112
        return $toc;
3113
    }
3114
3115
    /**
3116
     * Returns the CSS class name associated with a given item status.
3117
     *
3118
     * @param $status string an item status
3119
     *
3120
     * @return string CSS class name
3121
     */
3122
    public static function getStatusCSSClassName($status)
3123
    {
3124
        if (array_key_exists($status, self::STATUS_CSS_CLASS_NAME)) {
3125
            return self::STATUS_CSS_CLASS_NAME[$status];
3126
        }
3127
3128
        return '';
3129
    }
3130
3131
    /**
3132
     * Generate the tree of contents for this learnpath as an associative array tree
3133
     * with keys id, title, status, type, description, path, parent_id, children
3134
     * (title and descriptions as secured)
3135
     * and clues for CSS class composition:
3136
     *  - booleans is_current, is_parent_of_current, is_chapter
3137
     *  - string status_css_class_name.
3138
     *
3139
     * @param $parentId int restrict returned list to children of this parent
3140
     *
3141
     * @return array TOC as a table
3142
     */
3143
    public function getTOCTree($parentId = 0)
3144
    {
3145
        $toc = [];
3146
        $currentItemId = $this->get_current_item_id();
3147
3148
        foreach ($this->ordered_items as $itemId) {
3149
            $item = $this->items[$itemId];
3150
            if ($item->get_parent() == $parentId) {
3151
                $title = $item->get_title();
3152
                if (empty($title)) {
3153
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $itemId);
3154
                }
3155
3156
                $itemData = [
3157
                    'id' => $itemId,
3158
                    'title' => Security::remove_XSS($title),
3159
                    'status' => $item->get_status(),
3160
                    'level' => $item->get_level(), // FIXME should not be needed
3161
                    'type' => $item->get_type(),
3162
                    'description' => Security::remove_XSS($item->get_description()),
3163
                    'path' => $item->get_path(),
3164
                    'parent_id' => $item->get_parent(),
3165
                    'children' => $this->getTOCTree($itemId),
3166
                    'is_current' => ($itemId == $currentItemId),
3167
                    'is_parent_of_current' => false,
3168
                    'is_chapter' => in_array($item->get_type(), self::getChapterTypes()),
3169
                    'status_css_class_name' => $this->getStatusCSSClassName($item->get_status()),
3170
                    'current_id' => $currentItemId, // FIXME should not be needed, not a property of item
3171
                ];
3172
3173
                if (!empty($itemData['children'])) {
3174
                    foreach ($itemData['children'] as $child) {
3175
                        if ($child['is_current'] || $child['is_parent_of_current']) {
3176
                            $itemData['is_parent_of_current'] = true;
3177
                            break;
3178
                        }
3179
                    }
3180
                }
3181
3182
                $toc[] = $itemData;
3183
            }
3184
        }
3185
3186
        return $toc;
3187
    }
3188
3189
    /**
3190
     * Generate and return the table of contents for this learnpath. The JS
3191
     * table returned is used inside of scorm_api.php.
3192
     *
3193
     * @param string $varname
3194
     *
3195
     * @return string A JS array variable construction
3196
     */
3197
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
3198
    {
3199
        $toc = $varname.' = new Array();';
3200
        foreach ($this->ordered_items as $item_id) {
3201
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
3202
        }
3203
3204
        return $toc;
3205
    }
3206
3207
    /**
3208
     * Gets the learning path type.
3209
     *
3210
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
3211
     *
3212
     * @return mixed Type ID or name, depending on the parameter
3213
     */
3214
    public function get_type($get_name = false)
3215
    {
3216
        $res = false;
3217
        if (!empty($this->type) && (!$get_name)) {
3218
            $res = $this->type;
3219
        }
3220
3221
        return $res;
3222
    }
3223
3224
    /**
3225
     * Gets the learning path type as static method.
3226
     *
3227
     * @param int $lp_id
3228
     *
3229
     * @return mixed Returns the lp_type: 1 = Chamilo lms / 2 = SCORM
3230
     */
3231
    public static function get_type_static($lp_id = 0)
3232
    {
3233
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3234
        $lp_id = (int) $lp_id;
3235
        $sql = "SELECT lp_type FROM $tbl_lp
3236
                WHERE iid = $lp_id";
3237
        $res = Database::query($sql);
3238
        if ($res === false) {
3239
            return null;
3240
        }
3241
        if (Database::num_rows($res) <= 0) {
3242
            return null;
3243
        }
3244
        $row = Database::fetch_array($res);
3245
3246
        return $row['lp_type'];
3247
    }
3248
3249
    /**
3250
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3251
     * This method can be used as abstract and is recursive.
3252
     *
3253
     * @param int $lp        Learnpath ID
3254
     * @param int $parent    Parent ID of the items to look for
3255
     * @param int $course_id
3256
     *
3257
     * @return array Ordered list of item IDs (empty array on error)
3258
     */
3259
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3260
    {
3261
        if (empty($course_id)) {
3262
            $course_id = api_get_course_int_id();
3263
        } else {
3264
            $course_id = (int) $course_id;
3265
        }
3266
        $list = [];
3267
3268
        if (empty($lp)) {
3269
            return $list;
3270
        }
3271
3272
        $lp = (int) $lp;
3273
        $parent = (int) $parent;
3274
3275
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3276
        $sql = "SELECT iid FROM $tbl_lp_item
3277
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3278
                ORDER BY display_order";
3279
3280
        $res = Database::query($sql);
3281
        while ($row = Database::fetch_array($res)) {
3282
            $sublist = self::get_flat_ordered_items_list(
3283
                $lp,
3284
                $row['iid'],
3285
                $course_id
3286
            );
3287
            $list[] = $row['iid'];
3288
            foreach ($sublist as $item) {
3289
                $list[] = $item;
3290
            }
3291
        }
3292
3293
        return $list;
3294
    }
3295
3296
    /**
3297
     * @return array
3298
     */
3299
    public static function getChapterTypes()
3300
    {
3301
        return [
3302
            'dir',
3303
        ];
3304
    }
3305
3306
    /**
3307
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3308
     *
3309
     * @param $tree
3310
     *
3311
     * @return array HTML TOC ready to display
3312
     */
3313
    public function getParentToc($tree)
3314
    {
3315
        if (empty($tree)) {
3316
            $tree = $this->get_toc();
3317
        }
3318
        $dirTypes = self::getChapterTypes();
3319
        $myCurrentId = $this->get_current_item_id();
3320
        $listParent = [];
3321
        $listChildren = [];
3322
        $listNotParent = [];
3323
        $list = [];
3324
        foreach ($tree as $subtree) {
3325
            if (in_array($subtree['type'], $dirTypes)) {
3326
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3327
                $subtree['children'] = $listChildren;
3328
                if (!empty($subtree['children'])) {
3329
                    foreach ($subtree['children'] as $subItem) {
3330
                        if ($subItem['id'] == $this->current) {
3331
                            $subtree['parent_current'] = 'in';
3332
                            $subtree['current'] = 'on';
3333
                        }
3334
                    }
3335
                }
3336
                $listParent[] = $subtree;
3337
            }
3338
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
3339
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3340
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3341
                }
3342
3343
                $title = Security::remove_XSS($subtree['title']);
3344
                unset($subtree['title']);
3345
3346
                if (empty($title)) {
3347
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3348
                }
3349
                $classStyle = null;
3350
                if ($subtree['id'] == $this->current) {
3351
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3352
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3353
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3354
                }
3355
                $subtree['title'] = $title;
3356
                $subtree['class'] = $classStyle.' '.$cssStatus;
3357
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3358
                $subtree['current_id'] = $myCurrentId;
3359
                $listNotParent[] = $subtree;
3360
            }
3361
        }
3362
3363
        $list['are_parents'] = $listParent;
3364
        $list['not_parents'] = $listNotParent;
3365
3366
        return $list;
3367
    }
3368
3369
    /**
3370
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3371
     *
3372
     * @param array $tree
3373
     * @param int   $id
3374
     * @param bool  $parent
3375
     *
3376
     * @return array HTML TOC ready to display
3377
     */
3378
    public function getChildrenToc($tree, $id, $parent = true)
3379
    {
3380
        if (empty($tree)) {
3381
            $tree = $this->get_toc();
3382
        }
3383
3384
        $dirTypes = self::getChapterTypes();
3385
        $currentItemId = $this->get_current_item_id();
3386
        $list = [];
3387
3388
        foreach ($tree as $subtree) {
3389
            $subtree['tree'] = null;
3390
3391
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3392
                if ($subtree['id'] == $this->current) {
3393
                    $subtree['current'] = 'active';
3394
                } else {
3395
                    $subtree['current'] = null;
3396
                }
3397
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3398
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3399
                }
3400
3401
                $title = Security::remove_XSS($subtree['title']);
3402
                unset($subtree['title']);
3403
                if (empty($title)) {
3404
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3405
                }
3406
3407
                $classStyle = null;
3408
                if ($subtree['id'] == $this->current) {
3409
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3410
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3411
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3412
                }
3413
3414
                if (in_array($subtree['type'], $dirTypes)) {
3415
                    $subtree['title'] = stripslashes($title);
3416
                } else {
3417
                    $subtree['title'] = $title;
3418
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3419
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3420
                    $subtree['current_id'] = $currentItemId;
3421
                }
3422
                $list[] = $subtree;
3423
            }
3424
        }
3425
3426
        return $list;
3427
    }
3428
3429
    /**
3430
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3431
     *
3432
     * @param array $toc_list
3433
     *
3434
     * @return array HTML TOC ready to display
3435
     */
3436
    public function getListArrayToc($toc_list = [])
3437
    {
3438
        if (empty($toc_list)) {
3439
            $toc_list = $this->get_toc();
3440
        }
3441
        // Temporary variables.
3442
        $currentItemId = $this->get_current_item_id();
3443
        $list = [];
3444
        $arrayList = [];
3445
3446
        foreach ($toc_list as $item) {
3447
            $list['id'] = $item['id'];
3448
            $list['status'] = $item['status'];
3449
            $cssStatus = null;
3450
3451
            if (array_key_exists($item['status'], self::STATUS_CSS_CLASS_NAME)) {
3452
                $cssStatus = self::STATUS_CSS_CLASS_NAME[$item['status']];
3453
            }
3454
3455
            $classStyle = ' ';
3456
            $dirTypes = self::getChapterTypes();
3457
3458
            if (in_array($item['type'], $dirTypes)) {
3459
                $classStyle = 'scorm_item_section ';
3460
            }
3461
            if ($item['id'] == $this->current) {
3462
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3463
            } elseif (!in_array($item['type'], $dirTypes)) {
3464
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3465
            }
3466
            $title = $item['title'];
3467
            if (empty($title)) {
3468
                $title = self::rl_get_resource_name(
3469
                    api_get_course_id(),
3470
                    $this->get_id(),
3471
                    $item['id']
3472
                );
3473
            }
3474
            $title = Security::remove_XSS($item['title']);
3475
3476
            if (empty($item['description'])) {
3477
                $list['description'] = $title;
3478
            } else {
3479
                $list['description'] = $item['description'];
3480
            }
3481
3482
            $list['class'] = $classStyle.' '.$cssStatus;
3483
            $list['level'] = $item['level'];
3484
            $list['type'] = $item['type'];
3485
3486
            if (in_array($item['type'], $dirTypes)) {
3487
                $list['css_level'] = 'level_'.$item['level'];
3488
            } else {
3489
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3490
            }
3491
3492
            if (in_array($item['type'], $dirTypes)) {
3493
                $list['title'] = stripslashes($title);
3494
            } else {
3495
                $list['title'] = stripslashes($title);
3496
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3497
                $list['current_id'] = $currentItemId;
3498
            }
3499
            $arrayList[] = $list;
3500
        }
3501
3502
        return $arrayList;
3503
    }
3504
3505
    /**
3506
     * Returns an HTML-formatted string ready to display with teacher buttons
3507
     * in LP view menu.
3508
     *
3509
     * @return string HTML TOC ready to display
3510
     */
3511
    public function get_teacher_toc_buttons()
3512
    {
3513
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3514
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3515
        $html = '';
3516
        if ($isAllow && $hideIcons == false) {
3517
            if ($this->get_lp_session_id() == api_get_session_id()) {
3518
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3519
                $html .= '<div class="btn-group">';
3520
                $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'>".
3521
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3522
                $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'>".
3523
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3524
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3525
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3526
                $html .= '</div>';
3527
                $html .= '</div>';
3528
            }
3529
        }
3530
3531
        return $html;
3532
    }
3533
3534
    /**
3535
     * Returns an HTML-formatted string ready to display flow buttons
3536
     * in LP view menu.
3537
     *
3538
     * @return string HTML TOC ready to display
3539
     */
3540
    public function getFlowLpbuttons()
3541
    {
3542
        $allowFlowButtons = api_get_configuration_value('lp_enable_flow');
3543
        $html = '';
3544
        if ($allowFlowButtons) {
3545
            $nextLpId = self::getFlowNextLpId($this->lp_id, api_get_course_int_id());
3546
            $prevLpId = self::getFlowPrevLpId($this->lp_id, api_get_course_int_id());
3547
            if (!empty($nextLpId) || !empty($prevLpId)) {
3548
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3549
                $html .= '<div class="btn-group">';
3550
                if ($prevLpId > 0) {
3551
                    $html .= "<a class='btn btn-sm btn-default' href='lp_controller.php?".api_get_cidreq()."&action=view&lp_id=".$prevLpId."' target='_parent'>".
3552
                        Display::returnFontAwesomeIcon('arrow-left').'&nbsp;'.get_lang('Previous')."</a>";
3553
                }
3554
                if ($nextLpId > 0) {
3555
                    $html .= "<a class='btn btn-sm btn-default' href='lp_controller.php?".api_get_cidreq()."&action=view&lp_id=".$nextLpId."' target='_parent'>".
3556
                        get_lang('Next').'&nbsp;'.Display::returnFontAwesomeIcon('arrow-right')."</a>";
3557
                }
3558
                $html .= '</div>';
3559
                $html .= '</div>';
3560
            }
3561
        }
3562
3563
        return $html;
3564
    }
3565
3566
    /**
3567
     * Gets the learnpath maker name - generally the editor's name.
3568
     *
3569
     * @return string Learnpath maker name
3570
     */
3571
    public function get_maker()
3572
    {
3573
        if (!empty($this->maker)) {
3574
            return $this->maker;
3575
        }
3576
3577
        return '';
3578
    }
3579
3580
    /**
3581
     * Gets the learnpath name/title.
3582
     *
3583
     * @return string Learnpath name/title
3584
     */
3585
    public function get_name()
3586
    {
3587
        if (!empty($this->name)) {
3588
            return $this->name;
3589
        }
3590
3591
        return 'N/A';
3592
    }
3593
3594
    /**
3595
     * @return string
3596
     */
3597
    public function getNameNoTags()
3598
    {
3599
        return Security::remove_XSS(strip_tags($this->get_name()));
3600
    }
3601
3602
    /**
3603
     * Gets a link to the resource from the present location, depending on item ID.
3604
     *
3605
     * @param string $type         Type of link expected
3606
     * @param int    $item_id      Learnpath item ID
3607
     * @param bool   $provided_toc
3608
     *
3609
     * @return string $provided_toc Link to the lp_item resource
3610
     */
3611
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
3612
    {
3613
        $course_id = $this->get_course_int_id();
3614
        $item_id = (int) $item_id;
3615
3616
        if (empty($item_id)) {
3617
            $item_id = $this->get_current_item_id();
3618
3619
            if (empty($item_id)) {
3620
                //still empty, this means there was no item_id given and we are not in an object context or
3621
                //the object property is empty, return empty link
3622
                $this->first();
3623
3624
                return '';
3625
            }
3626
        }
3627
3628
        $file = '';
3629
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3630
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3631
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3632
3633
        $sql = "SELECT
3634
                    l.lp_type as ltype,
3635
                    l.path as lpath,
3636
                    li.item_type as litype,
3637
                    li.path as lipath,
3638
                    li.parameters as liparams
3639
        		FROM $lp_table l
3640
                INNER JOIN $lp_item_table li
3641
                ON (li.lp_id = l.iid)
3642
        		WHERE
3643
        		    li.iid = $item_id
3644
        		";
3645
        $res = Database::query($sql);
3646
        if (Database::num_rows($res) > 0) {
3647
            $row = Database::fetch_array($res);
3648
            $lp_type = $row['ltype'];
3649
            $lp_path = $row['lpath'];
3650
            $lp_item_type = $row['litype'];
3651
            $lp_item_path = $row['lipath'];
3652
            $lp_item_params = $row['liparams'];
3653
3654
            if (empty($lp_item_params) && strpos($lp_item_path, '?') !== false) {
3655
                [$lp_item_path, $lp_item_params] = explode('?', $lp_item_path);
3656
            }
3657
            $sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3658
            if ($type === 'http') {
3659
                //web path
3660
                $course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3661
            } else {
3662
                $course_path = $sys_course_path; //system path
3663
            }
3664
3665
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3666
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3667
            if (in_array(
3668
                $lp_item_type,
3669
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication', 'xapi', 'h5p', 'survey']
3670
            )
3671
            ) {
3672
                $lp_type = 1;
3673
            }
3674
3675
            // Now go through the specific cases to get the end of the path
3676
            // @todo Use constants instead of int values.
3677
            switch ($lp_type) {
3678
                case 1:
3679
                    $file = self::rl_get_resource_link_for_learnpath(
3680
                        $course_id,
3681
                        $this->get_id(),
3682
                        $item_id,
3683
                        $this->get_view_id(),
3684
                        $this->get_lp_session_id()
3685
                    );
3686
                    switch ($lp_item_type) {
3687
                        case 'document':
3688
                            // Shows a button to download the file instead of just downloading the file directly.
3689
                            $documentPathInfo = pathinfo($file);
3690
                            if (isset($documentPathInfo['extension'])) {
3691
                                $parsed = parse_url($documentPathInfo['extension']);
3692
                                if (isset($parsed['path'])) {
3693
                                    $extension = $parsed['path'];
3694
                                    $extensionsToDownload = [
3695
                                        'zip',
3696
                                        'ppt',
3697
                                        'pptx',
3698
                                        'ods',
3699
                                        'xlsx',
3700
                                        'xls',
3701
                                        'csv',
3702
                                        'doc',
3703
                                        'docx',
3704
                                        'dot',
3705
                                    ];
3706
3707
                                    if (in_array($extension, $extensionsToDownload)) {
3708
                                        $file = api_get_path(WEB_CODE_PATH).
3709
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
3710
                                    }
3711
                                }
3712
                            }
3713
                            break;
3714
                        case 'dir':
3715
                            $file = 'lp_content.php?type=dir&'.api_get_cidreq();
3716
                            break;
3717
                        case 'link':
3718
                            if (!empty($file)) {
3719
                                if (Link::is_youtube_link($file)) {
3720
                                    $src = Link::get_youtube_video_id($file);
3721
                                    $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3722
                                } elseif (Link::isVimeoLink($file)) {
3723
                                    $src = Link::getVimeoLinkId($file);
3724
                                    $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3725
                                } else {
3726
                                    // If the current site is HTTPS and the link is
3727
                                    // HTTP, browsers will refuse opening the link
3728
                                    $urlId = api_get_current_access_url_id();
3729
                                    $url = api_get_access_url($urlId, false);
3730
                                    $protocol = substr($url['url'], 0, 5);
3731
                                    if ($protocol === 'https') {
3732
                                        $linkProtocol = substr($file, 0, 5);
3733
                                        if ($linkProtocol === 'http:') {
3734
                                            //this is the special intervention case
3735
                                            $file = api_get_path(
3736
                                                    WEB_CODE_PATH
3737
                                                ).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3738
                                        }
3739
                                    }
3740
                                }
3741
                            }
3742
                            break;
3743
                        case 'quiz':
3744
                            // Check how much attempts of a exercise exits in lp
3745
                            $lp_item_id = $this->get_current_item_id();
3746
                            $lp_view_id = $this->get_view_id();
3747
3748
                            $prevent_reinit = null;
3749
                            if (isset($this->items[$this->current])) {
3750
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3751
                            }
3752
3753
                            if (empty($provided_toc)) {
3754
                                if ($this->debug > 0) {
3755
                                    error_log('In learnpath::get_link() Loading get_toc ', 0);
3756
                                }
3757
                                $list = $this->get_toc();
3758
                            } else {
3759
                                if ($this->debug > 0) {
3760
                                    error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
3761
                                }
3762
                                $list = $provided_toc;
3763
                            }
3764
3765
                            $type_quiz = false;
3766
                            foreach ($list as $toc) {
3767
                                if ($toc['id'] == $lp_item_id && $toc['type'] === 'quiz') {
3768
                                    $type_quiz = true;
3769
                                }
3770
                            }
3771
3772
                            if ($type_quiz) {
3773
                                $lp_item_id = (int) $lp_item_id;
3774
                                $lp_view_id = (int) $lp_view_id;
3775
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3776
                                        WHERE
3777
                                            c_id = $course_id AND
3778
                                            lp_item_id='".$lp_item_id."' AND
3779
                                            lp_view_id ='".$lp_view_id."' AND
3780
                                            status='completed'";
3781
                                $result = Database::query($sql);
3782
                                $row_count = Database::fetch_row($result);
3783
                                $count_item_view = (int) $row_count[0];
3784
                                $not_multiple_attempt = 0;
3785
                                if ($prevent_reinit === 1 && $count_item_view > 0) {
3786
                                    $not_multiple_attempt = 1;
3787
                                }
3788
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3789
                            }
3790
                            break;
3791
                    }
3792
3793
                    $tmp_array = explode('/', $file);
3794
                    $document_name = $tmp_array[count($tmp_array) - 1];
3795
                    if (strpos($document_name, '_DELETED_')) {
3796
                        $file = 'blank.php?error=document_deleted';
3797
                    }
3798
                    break;
3799
                case 2:
3800
                    if ($this->debug > 2) {
3801
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3802
                    }
3803
3804
                    if ($lp_item_type != 'dir') {
3805
                        // Quite complex here:
3806
                        // We want to make sure 'http://' (and similar) links can
3807
                        // be loaded as is (withouth the Chamilo path in front) but
3808
                        // some contents use this form: resource.htm?resource=http://blablabla
3809
                        // which means we have to find a protocol at the path's start, otherwise
3810
                        // it should not be considered as an external URL.
3811
                        // if ($this->prerequisites_match($item_id)) {
3812
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3813
                            if ($this->debug > 2) {
3814
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3815
                            }
3816
                            // Distant url, return as is.
3817
                            $file = $lp_item_path;
3818
                        } else {
3819
                            if ($this->debug > 2) {
3820
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3821
                            }
3822
                            // Prevent getting untranslatable urls.
3823
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3824
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3825
                            // Prepare the path.
3826
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3827
                            // TODO: Fix this for urls with protocol header.
3828
                            $file = str_replace('//', '/', $file);
3829
                            $file = str_replace(':/', '://', $file);
3830
                            if (substr($lp_path, -1) == '/') {
3831
                                $lp_path = substr($lp_path, 0, -1);
3832
                            }
3833
3834
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3835
                                // if file not found.
3836
                                $decoded = html_entity_decode($lp_item_path);
3837
                                [$decoded] = explode('?', $decoded);
3838
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3839
                                    $file = self::rl_get_resource_link_for_learnpath(
3840
                                        $course_id,
3841
                                        $this->get_id(),
3842
                                        $item_id,
3843
                                        $this->get_view_id()
3844
                                    );
3845
                                    if (empty($file)) {
3846
                                        $file = 'blank.php?error=document_not_found';
3847
                                    } else {
3848
                                        $tmp_array = explode('/', $file);
3849
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3850
                                        if (strpos($document_name, '_DELETED_')) {
3851
                                            $file = 'blank.php?error=document_deleted';
3852
                                        } else {
3853
                                            $file = 'blank.php?error=document_not_found';
3854
                                        }
3855
                                    }
3856
                                } else {
3857
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3858
                                }
3859
                            }
3860
                        }
3861
3862
                        // We want to use parameters if they were defined in the imsmanifest
3863
                        if (strpos($file, 'blank.php') === false) {
3864
                            $lp_item_params = ltrim($lp_item_params, '?');
3865
                            $file .= (strstr($file, '?') === false ? '?' : '').$lp_item_params;
3866
                        }
3867
                    } else {
3868
                        $file = 'lp_content.php?type=dir&'.api_get_cidreq();
3869
                    }
3870
                    break;
3871
                case 3:
3872
                    if ($this->debug > 2) {
3873
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3874
                    }
3875
                    // Formatting AICC HACP append URL.
3876
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3877
                    if (!empty($lp_item_params)) {
3878
                        $aicc_append .= $lp_item_params.'&';
3879
                    }
3880
                    if ($lp_item_type != 'dir') {
3881
                        // Quite complex here:
3882
                        // We want to make sure 'http://' (and similar) links can
3883
                        // be loaded as is (withouth the Chamilo path in front) but
3884
                        // some contents use this form: resource.htm?resource=http://blablabla
3885
                        // which means we have to find a protocol at the path's start, otherwise
3886
                        // it should not be considered as an external URL.
3887
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3888
                            if ($this->debug > 2) {
3889
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3890
                            }
3891
                            // Distant url, return as is.
3892
                            $file = $lp_item_path;
3893
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3894
                            /*
3895
                            if (stristr($file,'<servername>') !== false) {
3896
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3897
                            }
3898
                            */
3899
                            if (stripos($file, '<servername>') !== false) {
3900
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3901
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3902
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3903
                            }
3904
3905
                            $file .= $aicc_append;
3906
                        } else {
3907
                            if ($this->debug > 2) {
3908
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3909
                            }
3910
                            // Prevent getting untranslatable urls.
3911
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3912
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3913
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3914
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3915
                            // TODO: Fix this for urls with protocol header.
3916
                            $file = str_replace('//', '/', $file);
3917
                            $file = str_replace(':/', '://', $file);
3918
                            $file .= $aicc_append;
3919
                        }
3920
                    } else {
3921
                        $file = 'lp_content.php?type=dir&'.api_get_cidreq();
3922
                    }
3923
                    break;
3924
                case 4:
3925
                    break;
3926
                default:
3927
                    break;
3928
            }
3929
            // Replace &amp; by & because &amp; will break URL with params
3930
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3931
        }
3932
        if ($this->debug > 2) {
3933
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3934
        }
3935
3936
        return $file;
3937
    }
3938
3939
    /**
3940
     * Gets the latest usable view or generate a new one.
3941
     *
3942
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3943
     * @param int $userId      The user ID, as $this->get_user_id() is not always available
3944
     *
3945
     * @return int DB lp_view id
3946
     */
3947
    public function get_view($attempt_num = 0, $userId = null)
3948
    {
3949
        $search = '';
3950
        // Use $attempt_num to enable multi-views management (disabled so far).
3951
        if ($attempt_num != 0 && intval(strval($attempt_num)) == $attempt_num) {
3952
            $search = 'AND view_count = '.$attempt_num;
3953
        }
3954
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3955
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3956
3957
        $course_id = api_get_course_int_id();
3958
        $sessionId = api_get_session_id();
3959
3960
        // Check user ID.
3961
        if (empty($userId)) {
3962
            if (empty($this->get_user_id())) {
3963
                $this->error = 'User ID is empty in learnpath::get_view()';
3964
3965
                return null;
3966
            } else {
3967
                $userId = $this->get_user_id();
3968
            }
3969
        }
3970
3971
        $sql = "SELECT iid, view_count FROM $lp_view_table
3972
        		WHERE
3973
        		    c_id = $course_id AND
3974
        		    lp_id = ".$this->get_id()." AND
3975
        		    user_id = ".$userId." AND
3976
        		    session_id = $sessionId
3977
        		    $search
3978
                ORDER BY view_count DESC";
3979
        $res = Database::query($sql);
3980
        if (Database::num_rows($res) > 0) {
3981
            $row = Database::fetch_array($res);
3982
            $this->lp_view_id = $row['iid'];
3983
        } elseif (!api_is_invitee()) {
3984
            // There is no database record, create one.
3985
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3986
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3987
            Database::query($sql);
3988
            $id = Database::insert_id();
3989
            $this->lp_view_id = $id;
3990
3991
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
3992
            Database::query($sql);
3993
        }
3994
3995
        return $this->lp_view_id;
3996
    }
3997
3998
    /**
3999
     * Gets the current view id.
4000
     *
4001
     * @return int View ID (from lp_view)
4002
     */
4003
    public function get_view_id()
4004
    {
4005
        if (!empty($this->lp_view_id)) {
4006
            return (int) $this->lp_view_id;
4007
        }
4008
4009
        return 0;
4010
    }
4011
4012
    /**
4013
     * Gets the update queue.
4014
     *
4015
     * @return array Array containing IDs of items to be updated by JavaScript
4016
     */
4017
    public function get_update_queue()
4018
    {
4019
        return $this->update_queue;
4020
    }
4021
4022
    /**
4023
     * Gets the user ID.
4024
     *
4025
     * @return int User ID
4026
     */
4027
    public function get_user_id()
4028
    {
4029
        if (!empty($this->user_id)) {
4030
            return (int) $this->user_id;
4031
        }
4032
4033
        return false;
4034
    }
4035
4036
    /**
4037
     * Checks if any of the items has an audio element attached.
4038
     *
4039
     * @return bool True or false
4040
     */
4041
    public function has_audio()
4042
    {
4043
        $has = false;
4044
        foreach ($this->items as $i => $item) {
4045
            if (!empty($this->items[$i]->audio)) {
4046
                $has = true;
4047
                break;
4048
            }
4049
        }
4050
4051
        return $has;
4052
    }
4053
4054
    /**
4055
     * Moves an item up and down at its level.
4056
     *
4057
     * @param int    $id        Item to move up and down
4058
     * @param string $direction Direction 'up' or 'down'
4059
     *
4060
     * @return bool|int
4061
     */
4062
    public function move_item($id, $direction)
4063
    {
4064
        $course_id = api_get_course_int_id();
4065
        if (empty($id) || empty($direction)) {
4066
            return false;
4067
        }
4068
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
4069
        $sql_sel = "SELECT *
4070
                    FROM $tbl_lp_item
4071
                    WHERE
4072
                        iid = $id
4073
                    ";
4074
        $res_sel = Database::query($sql_sel);
4075
        // Check if elem exists.
4076
        if (Database::num_rows($res_sel) < 1) {
4077
            return false;
4078
        }
4079
        // Gather data.
4080
        $row = Database::fetch_array($res_sel);
4081
        $previous = $row['previous_item_id'];
4082
        $next = $row['next_item_id'];
4083
        $display = $row['display_order'];
4084
        $parent = $row['parent_item_id'];
4085
        $lp = $row['lp_id'];
4086
        // Update the item (switch with previous/next one).
4087
        switch ($direction) {
4088
            case 'up':
4089
                if ($display > 1) {
4090
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4091
                                 WHERE iid = $previous";
4092
                    $res_sel2 = Database::query($sql_sel2);
4093
                    if (Database::num_rows($res_sel2) < 1) {
4094
                        $previous_previous = 0;
4095
                    }
4096
                    // Gather data.
4097
                    $row2 = Database::fetch_array($res_sel2);
4098
                    $previous_previous = $row2['previous_item_id'];
4099
                    // Update previous_previous item (switch "next" with current).
4100
                    if ($previous_previous != 0) {
4101
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4102
                                        next_item_id = $id
4103
                                    WHERE iid = $previous_previous";
4104
                        Database::query($sql_upd2);
4105
                    }
4106
                    // Update previous item (switch with current).
4107
                    if ($previous != 0) {
4108
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4109
                                    next_item_id = $next,
4110
                                    previous_item_id = $id,
4111
                                    display_order = display_order +1
4112
                                    WHERE iid = $previous";
4113
                        Database::query($sql_upd2);
4114
                    }
4115
4116
                    // Update current item (switch with previous).
4117
                    if ($id != 0) {
4118
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4119
                                        next_item_id = $previous,
4120
                                        previous_item_id = $previous_previous,
4121
                                        display_order = display_order-1
4122
                                    WHERE c_id = ".$course_id." AND id = $id";
4123
                        Database::query($sql_upd2);
4124
                    }
4125
                    // Update next item (new previous item).
4126
                    if (!empty($next)) {
4127
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
4128
                                     WHERE iid = $next";
4129
                        Database::query($sql_upd2);
4130
                    }
4131
                    $display = $display - 1;
4132
                }
4133
                break;
4134
            case 'down':
4135
                if ($next != 0) {
4136
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4137
                                 WHERE iid = $next";
4138
                    $res_sel2 = Database::query($sql_sel2);
4139
                    if (Database::num_rows($res_sel2) < 1) {
4140
                        $next_next = 0;
4141
                    }
4142
                    // Gather data.
4143
                    $row2 = Database::fetch_array($res_sel2);
4144
                    $next_next = $row2['next_item_id'];
4145
                    // Update previous item (switch with current).
4146
                    if ($previous != 0) {
4147
                        $sql_upd2 = "UPDATE $tbl_lp_item
4148
                                     SET next_item_id = $next
4149
                                     WHERE iid = $previous";
4150
                        Database::query($sql_upd2);
4151
                    }
4152
                    // Update current item (switch with previous).
4153
                    if ($id != 0) {
4154
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4155
                                     previous_item_id = $next,
4156
                                     next_item_id = $next_next,
4157
                                     display_order = display_order + 1
4158
                                     WHERE iid = $id";
4159
                        Database::query($sql_upd2);
4160
                    }
4161
4162
                    // Update next item (new previous item).
4163
                    if ($next != 0) {
4164
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4165
                                     previous_item_id = $previous,
4166
                                     next_item_id = $id,
4167
                                     display_order = display_order-1
4168
                                     WHERE iid = $next";
4169
                        Database::query($sql_upd2);
4170
                    }
4171
4172
                    // Update next_next item (switch "previous" with current).
4173
                    if ($next_next != 0) {
4174
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4175
                                     previous_item_id = $id
4176
                                     WHERE iid = $next_next";
4177
                        Database::query($sql_upd2);
4178
                    }
4179
                    $display = $display + 1;
4180
                }
4181
                break;
4182
            default:
4183
                return false;
4184
        }
4185
4186
        return $display;
4187
    }
4188
4189
    /**
4190
     * Move a LP up (display_order).
4191
     *
4192
     * @param int $lp_id      Learnpath ID
4193
     * @param int $categoryId Category ID
4194
     *
4195
     * @return bool
4196
     */
4197
    public static function move_up($lp_id, $categoryId = 0)
4198
    {
4199
        $courseId = api_get_course_int_id();
4200
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4201
4202
        $categoryCondition = '';
4203
        if (!empty($categoryId)) {
4204
            $categoryId = (int) $categoryId;
4205
            $categoryCondition = " AND category_id = $categoryId";
4206
        }
4207
        $sql = "SELECT * FROM $lp_table
4208
                WHERE c_id = $courseId
4209
                $categoryCondition
4210
                ORDER BY display_order";
4211
        $res = Database::query($sql);
4212
        if ($res === false) {
4213
            return false;
4214
        }
4215
4216
        $lps = [];
4217
        $lp_order = [];
4218
        $num = Database::num_rows($res);
4219
        // First check the order is correct, globally (might be wrong because
4220
        // of versions < 1.8.4)
4221
        if ($num > 0) {
4222
            $i = 1;
4223
            while ($row = Database::fetch_array($res)) {
4224
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
4225
                    $sql = "UPDATE $lp_table SET display_order = $i
4226
                            WHERE iid = ".$row['iid'];
4227
                    Database::query($sql);
4228
                }
4229
                $row['display_order'] = $i;
4230
                $lps[$row['iid']] = $row;
4231
                $lp_order[$i] = $row['iid'];
4232
                $i++;
4233
            }
4234
        }
4235
        if ($num > 1) { // If there's only one element, no need to sort.
4236
            $order = $lps[$lp_id]['display_order'];
4237
            if ($order > 1) { // If it's the first element, no need to move up.
4238
                $sql = "UPDATE $lp_table SET display_order = $order
4239
                        WHERE iid = ".$lp_order[$order - 1];
4240
                Database::query($sql);
4241
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
4242
                        WHERE iid = $lp_id";
4243
                Database::query($sql);
4244
            }
4245
        }
4246
4247
        return true;
4248
    }
4249
4250
    /**
4251
     * Move a learnpath down (display_order).
4252
     *
4253
     * @param int $lp_id      Learnpath ID
4254
     * @param int $categoryId Category ID
4255
     *
4256
     * @return bool
4257
     */
4258
    public static function move_down($lp_id, $categoryId = 0)
4259
    {
4260
        $courseId = api_get_course_int_id();
4261
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4262
4263
        $categoryCondition = '';
4264
        if (!empty($categoryId)) {
4265
            $categoryId = (int) $categoryId;
4266
            $categoryCondition = " AND category_id = $categoryId";
4267
        }
4268
4269
        $sql = "SELECT * FROM $lp_table
4270
                WHERE c_id = $courseId
4271
                $categoryCondition
4272
                ORDER BY display_order";
4273
        $res = Database::query($sql);
4274
        if ($res === false) {
4275
            return false;
4276
        }
4277
        $lps = [];
4278
        $lp_order = [];
4279
        $num = Database::num_rows($res);
4280
        $max = 0;
4281
        // First check the order is correct, globally (might be wrong because
4282
        // of versions < 1.8.4).
4283
        if ($num > 0) {
4284
            $i = 1;
4285
            while ($row = Database::fetch_array($res)) {
4286
                $max = $i;
4287
                if ($row['display_order'] != $i) {
4288
                    // If we find a gap in the order, we need to fix it.
4289
                    $sql = "UPDATE $lp_table SET display_order = $i
4290
                              WHERE iid = ".$row['iid'];
4291
                    Database::query($sql);
4292
                }
4293
                $row['display_order'] = $i;
4294
                $lps[$row['iid']] = $row;
4295
                $lp_order[$i] = $row['iid'];
4296
                $i++;
4297
            }
4298
        }
4299
        if ($num > 1) { // If there's only one element, no need to sort.
4300
            $order = $lps[$lp_id]['display_order'];
4301
            if ($order < $max) { // If it's the first element, no need to move up.
4302
                $sql = "UPDATE $lp_table SET display_order = $order
4303
                        WHERE iid = ".$lp_order[$order + 1];
4304
                Database::query($sql);
4305
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4306
                        WHERE iid = $lp_id";
4307
                Database::query($sql);
4308
            }
4309
        }
4310
4311
        return true;
4312
    }
4313
4314
    /**
4315
     * Updates learnpath attributes to point to the next element
4316
     * The last part is similar to set_current_item but processing the other way around.
4317
     */
4318
    public function next()
4319
    {
4320
        if ($this->debug > 0) {
4321
            error_log('In learnpath::next()', 0);
4322
        }
4323
        $this->last = $this->get_current_item_id();
4324
        $this->items[$this->last]->save(
4325
            false,
4326
            $this->prerequisites_match($this->last)
4327
        );
4328
        $this->autocomplete_parents($this->last);
4329
        $new_index = $this->get_next_index();
4330
        if ($this->debug > 2) {
4331
            error_log('New index: '.$new_index, 0);
4332
        }
4333
        $this->index = $new_index;
4334
        if ($this->debug > 2) {
4335
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4336
        }
4337
        $this->current = $this->ordered_items[$new_index];
4338
        if ($this->debug > 2) {
4339
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4340
        }
4341
    }
4342
4343
    /**
4344
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4345
     * class, this might be redefined to allow several behaviours depending on the document type.
4346
     *
4347
     * @param int $id Resource ID
4348
     */
4349
    public function open($id)
4350
    {
4351
        // TODO:
4352
        // set the current resource attribute to this resource
4353
        // switch on element type (redefine in child class?)
4354
        // set status for this item to "opened"
4355
        // start timer
4356
        // initialise score
4357
        $this->index = 0; //or = the last item seen (see $this->last)
4358
    }
4359
4360
    /**
4361
     * Check that all prerequisites are fulfilled. Returns true and an
4362
     * empty string on success, returns false
4363
     * and the prerequisite string on error.
4364
     * This function is based on the rules for aicc_script language as
4365
     * described in the SCORM 1.2 CAM documentation page 108.
4366
     *
4367
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4368
     *
4369
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4370
     *              string otherwise
4371
     */
4372
    public function prerequisites_match($itemId = null)
4373
    {
4374
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4375
        if ($allow) {
4376
            if (api_is_allowed_to_edit() ||
4377
                api_is_platform_admin(true) ||
4378
                api_is_drh() ||
4379
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4380
            ) {
4381
                return true;
4382
            }
4383
        }
4384
4385
        $debug = $this->debug;
4386
        if ($debug > 0) {
4387
            error_log('In learnpath::prerequisites_match()');
4388
        }
4389
4390
        if (empty($itemId)) {
4391
            $itemId = $this->current;
4392
        }
4393
4394
        $currentItem = $this->getItem($itemId);
4395
        if ($debug > 0) {
4396
            error_log("Checking item id $itemId");
4397
        }
4398
4399
        if ($currentItem) {
4400
            if ($this->type == 2) {
4401
                // Getting prereq from scorm
4402
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4403
            } else {
4404
                $prereq_string = $currentItem->get_prereq_string();
4405
            }
4406
4407
            if (empty($prereq_string)) {
4408
                if ($debug > 0) {
4409
                    error_log('Found prereq_string is empty return true');
4410
                }
4411
                // checks the dates values as prerequisites
4412
                $result = $this->prerequistesDatesMatch($itemId);
4413
4414
                return $result;
4415
            }
4416
4417
            // Clean spaces.
4418
            $prereq_string = str_replace(' ', '', $prereq_string);
4419
            if ($debug > 0) {
4420
                error_log('Found prereq_string: '.$prereq_string, 0);
4421
            }
4422
4423
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4424
            $result = $currentItem->parse_prereq(
4425
                $prereq_string,
4426
                $this->items,
4427
                $this->refs_list,
4428
                $this->get_user_id()
4429
            );
4430
4431
            if ($result === false) {
4432
                $this->set_error_msg($currentItem->prereq_alert);
4433
            }
4434
        } else {
4435
            $result = true;
4436
            if ($debug > 1) {
4437
                error_log('$this->items['.$itemId.'] was not an object');
4438
            }
4439
        }
4440
4441
        if ($debug > 1) {
4442
            error_log('Result: '.$result);
4443
            error_log('End of prerequisites_match(). Error message is now '.$this->error);
4444
        }
4445
4446
        if (true === $result && $itemId) {
4447
            // checks the dates values as prerequisites
4448
            $result = $this->prerequistesDatesMatch($itemId);
4449
        }
4450
4451
        return $result;
4452
    }
4453
4454
    public function prerequistesDatesMatch(int $itemId)
4455
    {
4456
        if (true === api_get_configuration_value('lp_item_prerequisite_dates')) {
4457
            $extraFieldValue = new ExtraFieldValue('lp_item');
4458
            $startDate = $extraFieldValue->get_values_by_handler_and_field_variable(
4459
                $itemId,
4460
                'start_date'
4461
            );
4462
            $endDate = $extraFieldValue->get_values_by_handler_and_field_variable(
4463
                $itemId,
4464
                'end_date'
4465
            );
4466
4467
            $now = time();
4468
            $start = !empty($startDate['value']) ? api_strtotime($startDate['value']) : 0;
4469
            $end = !empty($endDate['value']) ? api_strtotime($endDate['value']) : 0;
4470
            $result = false;
4471
4472
            if (($start == 0 && $end == 0) ||
4473
                (($start > 0 && $end == 0) && $now > $start) ||
4474
                (($start == 0 && $end > 0) && $now < $end) ||
4475
                (($start > 0 && $end > 0) && ($now > $start && $now < $end))
4476
            ) {
4477
                $result = true;
4478
            }
4479
4480
            if (!$result) {
4481
                $errMsg = get_lang('ItemCanNotBeAccessedPrerequisiteDates');
4482
                if ($start > 0 && $start > $now) {
4483
                    $errMsg = get_lang('AccessibleFrom').' '.api_format_date($start, DATE_TIME_FORMAT_LONG);
4484
                }
4485
                if ($end > 0 && $end < $now) {
4486
                    $errMsg = get_lang('NoMoreAccessible');
4487
                }
4488
                $this->set_error_msg($errMsg);
4489
                $currentItem = $this->getItem($itemId);
4490
                $currentItem->prereq_alert = $errMsg;
4491
            }
4492
4493
            return $result;
4494
        }
4495
4496
        return true;
4497
    }
4498
4499
    /**
4500
     * Updates learnpath attributes to point to the previous element
4501
     * The last part is similar to set_current_item but processing the other way around.
4502
     */
4503
    public function previous()
4504
    {
4505
        $this->last = $this->get_current_item_id();
4506
        $this->items[$this->last]->save(
4507
            false,
4508
            $this->prerequisites_match($this->last)
4509
        );
4510
        $this->autocomplete_parents($this->last);
4511
        $new_index = $this->get_previous_index();
4512
        $this->index = $new_index;
4513
        $this->current = $this->ordered_items[$new_index];
4514
    }
4515
4516
    /**
4517
     * Publishes a learnpath. This basically means show or hide the learnpath
4518
     * to normal users.
4519
     * Can be used as abstract.
4520
     *
4521
     * @param int $lp_id          Learnpath ID
4522
     * @param int $set_visibility New visibility
4523
     *
4524
     * @return bool
4525
     */
4526
    public static function toggle_visibility($lp_id, $set_visibility = 1)
4527
    {
4528
        if (empty($lp_id)) {
4529
            return false;
4530
        }
4531
4532
        $action = 'visible';
4533
        if ($set_visibility != 1) {
4534
            $action = 'invisible';
4535
            self::toggle_publish($lp_id, 'i');
4536
        }
4537
4538
        return api_item_property_update(
4539
            api_get_course_info(),
4540
            TOOL_LEARNPATH,
4541
            $lp_id,
4542
            $action,
4543
            api_get_user_id()
4544
        );
4545
    }
4546
4547
    /**
4548
     * Publishes a learnpath category.
4549
     * This basically means show or hide the learnpath category to normal users.
4550
     *
4551
     * @param int $id
4552
     * @param int $visibility
4553
     *
4554
     * @return bool
4555
     */
4556
    public static function toggleCategoryVisibility($id, $visibility = 1)
4557
    {
4558
        $action = 'visible';
4559
        if ($visibility != 1) {
4560
            self::toggleCategoryPublish($id, 0);
4561
            $action = 'invisible';
4562
        }
4563
4564
        return api_item_property_update(
4565
            api_get_course_info(),
4566
            TOOL_LEARNPATH_CATEGORY,
4567
            $id,
4568
            $action,
4569
            api_get_user_id()
4570
        );
4571
    }
4572
4573
    /**
4574
     * Publishes a learnpath. This basically means show or hide the learnpath
4575
     * on the course homepage
4576
     * Can be used as abstract.
4577
     *
4578
     * @param int    $lp_id          Learnpath id
4579
     * @param string $set_visibility New visibility (v/i - visible/invisible)
4580
     *
4581
     * @return bool
4582
     */
4583
    public static function toggle_publish($lp_id, $set_visibility = 'v')
4584
    {
4585
        if (empty($lp_id)) {
4586
            return false;
4587
        }
4588
        $course_id = api_get_course_int_id();
4589
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4590
        $lp_id = (int) $lp_id;
4591
        $sql = "SELECT * FROM $tbl_lp
4592
                WHERE iid = $lp_id";
4593
        $result = Database::query($sql);
4594
        if (Database::num_rows($result)) {
4595
            $row = Database::fetch_array($result);
4596
            $name = Database::escape_string($row['name']);
4597
            if ($set_visibility === 'i') {
4598
                $v = 0;
4599
            }
4600
            if ($set_visibility === 'v') {
4601
                $v = 1;
4602
            }
4603
4604
            $session_id = api_get_session_id();
4605
            $session_condition = api_get_session_condition($session_id);
4606
4607
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4608
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4609
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4610
4611
            $extraLpCondition = '';
4612
            $extraLink = '';
4613
            if (!empty($session_id)) {
4614
                $extraLink = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session=0';
4615
                $extraLpCondition = " OR (link = '$link' AND session_id = $session_id )  ";
4616
            }
4617
4618
            $sql = "SELECT * FROM $tbl_tool
4619
                    WHERE
4620
                        c_id = $course_id AND
4621
                        (link = '$link' OR link = '$oldLink' $extraLpCondition ) AND
4622
                        image = 'scormbuilder.gif' AND
4623
                        (
4624
                            link LIKE '$link%' OR
4625
                            link LIKE '$oldLink%'
4626
                            $extraLpCondition
4627
                        )
4628
                        $session_condition
4629
                    ";
4630
4631
            $result = Database::query($sql);
4632
            $num = Database::num_rows($result);
4633
            $resultTool = Database::fetch_array($result, 'ASSOC');
4634
4635
            if ($set_visibility === 'i') {
4636
                if ($num > 0) {
4637
                    $sql = "DELETE FROM $tbl_tool
4638
                            WHERE
4639
                                c_id = $course_id AND
4640
                                (link = '$link' OR link = '$oldLink') AND
4641
                                image='scormbuilder.gif'
4642
                                $session_condition";
4643
                    Database::query($sql);
4644
                }
4645
4646
                // Disables the base course link inside a session.
4647
                if (!empty($session_id) && 0 === (int) $row['session_id']) {
4648
                    $sql = "SELECT iid FROM $tbl_tool
4649
                            WHERE
4650
                                c_id = $course_id AND
4651
                                (link = '$extraLink') AND
4652
                                image = 'scormbuilder.gif' AND
4653
                                session_id = $session_id
4654
                    ";
4655
                    $resultBaseLp = Database::query($sql);
4656
                    if (Database::num_rows($resultBaseLp)) {
4657
                        $resultBaseLpRow = Database::fetch_array($resultBaseLp);
4658
                        $id = $resultBaseLpRow['iid'];
4659
                        /*$sql = "UPDATE $tbl_tool
4660
                                SET visibility = 0
4661
                                WHERE iid = $id ";
4662
                        Database::query($sql);*/
4663
                        $sql = "DELETE FROM $tbl_tool
4664
                                WHERE iid = $id";
4665
                        Database::query($sql);
4666
                    } else {
4667
                        /*$params = [
4668
                            'category' => 'authoring',
4669
                            'c_id' => $course_id,
4670
                            'name' => $name,
4671
                            'link' => $link,
4672
                            'image' => 'scormbuilder.gif',
4673
                            'visibility' => '0',
4674
                            'admin' => '0',
4675
                            'address' => 'pastillegris.gif',
4676
                            'added_tool' => '0',
4677
                            'session_id' => $session_id,
4678
                        ];
4679
                        $insertId = Database::insert($tbl_tool, $params);
4680
                        if ($insertId) {
4681
                            $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4682
                            Database::query($sql);
4683
                        }*/
4684
                    }
4685
                }
4686
            }
4687
4688
            if ($set_visibility === 'v') {
4689
                if ($num == 0) {
4690
                    $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id)
4691
                            VALUES ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4692
                    Database::query($sql);
4693
                    $insertId = Database::insert_id();
4694
                    if ($insertId) {
4695
                        $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4696
                        Database::query($sql);
4697
                    }
4698
                }
4699
                if ($num > 0) {
4700
                    $id = $resultTool['iid'];
4701
                    $sql = "UPDATE $tbl_tool SET
4702
                        c_id = $course_id,
4703
                        name = '$name',
4704
                        link = '$link',
4705
                        image = 'scormbuilder.gif',
4706
                        visibility = '$v',
4707
                        admin = '0',
4708
                        address = 'pastillegris.gif',
4709
                        added_tool = 0,
4710
                        session_id = $session_id
4711
                    WHERE
4712
                        c_id = ".$course_id." AND
4713
                        iid = $id
4714
                    ";
4715
                    Database::query($sql);
4716
                }
4717
            }
4718
        }
4719
4720
        return false;
4721
    }
4722
4723
    /**
4724
     * Publishes a learnpath.
4725
     * Show or hide the learnpath category on the course homepage.
4726
     *
4727
     * @param int $id
4728
     * @param int $setVisibility
4729
     *
4730
     * @return bool
4731
     */
4732
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4733
    {
4734
        $courseId = api_get_course_int_id();
4735
        $sessionId = api_get_session_id();
4736
        $sessionCondition = api_get_session_condition(
4737
            $sessionId,
4738
            true,
4739
            false,
4740
            't.sessionId'
4741
        );
4742
4743
        $em = Database::getManager();
4744
        $category = self::getCategory($id);
4745
4746
        if (!$category) {
4747
            return false;
4748
        }
4749
4750
        if (empty($courseId)) {
4751
            return false;
4752
        }
4753
4754
        $link = self::getCategoryLinkForTool($id);
4755
4756
        /** @var CTool $tool */
4757
        $tool = $em->createQuery("
4758
                SELECT t FROM ChamiloCourseBundle:CTool t
4759
                WHERE
4760
                    t.cId = :course AND
4761
                    t.link = :link1 AND
4762
                    t.image = 'lp_category.gif' AND
4763
                    t.link LIKE :link2
4764
                    $sessionCondition
4765
            ")
4766
            ->setParameters([
4767
                'course' => $courseId,
4768
                'link1' => $link,
4769
                'link2' => "$link%",
4770
            ])
4771
            ->getOneOrNullResult();
4772
4773
        if ($setVisibility == 0 && $tool) {
4774
            $em->remove($tool);
4775
            $em->flush();
4776
4777
            return true;
4778
        }
4779
4780
        if ($setVisibility == 1 && !$tool) {
4781
            $tool = new CTool();
4782
            $tool
4783
                ->setCategory('authoring')
4784
                ->setCId($courseId)
4785
                ->setName(strip_tags($category->getName()))
4786
                ->setLink($link)
4787
                ->setImage('lp_category.gif')
4788
                ->setVisibility(1)
4789
                ->setAdmin(0)
4790
                ->setAddress('pastillegris.gif')
4791
                ->setAddedTool(0)
4792
                ->setSessionId($sessionId)
4793
                ->setTarget('_self');
4794
4795
            $em->persist($tool);
4796
            $em->flush();
4797
4798
            $tool->setId($tool->getIid());
4799
4800
            $em->persist($tool);
4801
            $em->flush();
4802
4803
            return true;
4804
        }
4805
4806
        if ($setVisibility == 1 && $tool) {
4807
            $tool
4808
                ->setName(strip_tags($category->getName()))
4809
                ->setVisibility(1);
4810
4811
            $em->persist($tool);
4812
            $em->flush();
4813
4814
            return true;
4815
        }
4816
4817
        return false;
4818
    }
4819
4820
    /**
4821
     * Check if the learnpath category is visible for a user.
4822
     *
4823
     * @param int
4824
     * @param int
4825
     *
4826
     * @return bool
4827
     */
4828
    public static function categoryIsVisibleForStudent(
4829
        CLpCategory $category,
4830
        User $user,
4831
        $courseId = 0,
4832
        $sessionId = 0
4833
    ) {
4834
        if (empty($category)) {
4835
            return false;
4836
        }
4837
4838
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4839
4840
        if ($isAllowedToEdit) {
4841
            return true;
4842
        }
4843
4844
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4845
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4846
4847
        $courseInfo = api_get_course_info_by_id($courseId);
4848
4849
        $categoryVisibility = api_get_item_visibility(
4850
            $courseInfo,
4851
            TOOL_LEARNPATH_CATEGORY,
4852
            $category->getId(),
4853
            $sessionId
4854
        );
4855
4856
        if ($categoryVisibility !== 1 && $categoryVisibility != -1) {
4857
            return false;
4858
        }
4859
4860
        $subscriptionSettings = self::getSubscriptionSettings();
4861
4862
        if ($subscriptionSettings['allow_add_users_to_lp_category'] == false) {
4863
            return true;
4864
        }
4865
4866
        $noUserSubscribed = false;
4867
        $noGroupSubscribed = true;
4868
        $users = $category->getUsers();
4869
        if (empty($users) || !$users->count()) {
4870
            $noUserSubscribed = true;
4871
        } elseif ($category->hasUserAdded($user)) {
4872
            return true;
4873
        }
4874
4875
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4876
        $em = Database::getManager();
4877
4878
        /** @var ItemPropertyRepository $itemRepo */
4879
        $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4880
4881
        /** @var CourseRepository $courseRepo */
4882
        $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4883
        $session = null;
4884
        if (!empty($sessionId)) {
4885
            $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4886
        }
4887
4888
        $course = $courseRepo->find($courseId);
4889
4890
        if ($courseId != 0) {
4891
            // Subscribed groups to a LP
4892
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4893
                TOOL_LEARNPATH_CATEGORY,
4894
                $category->getId(),
4895
                $course,
4896
                $session
4897
            );
4898
        }
4899
4900
        if (!empty($subscribedGroupsInLp)) {
4901
            $noGroupSubscribed = false;
4902
            if (!empty($groups)) {
4903
                $groups = array_column($groups, 'iid');
4904
                /** @var CItemProperty $item */
4905
                foreach ($subscribedGroupsInLp as $item) {
4906
                    if ($item->getGroup() &&
4907
                        in_array($item->getGroup()->getId(), $groups)
4908
                    ) {
4909
                        return true;
4910
                    }
4911
                }
4912
            }
4913
        }
4914
        $response = $noGroupSubscribed && $noUserSubscribed;
4915
4916
        return $response;
4917
    }
4918
4919
    /**
4920
     * Check if a learnpath category is published as course tool.
4921
     *
4922
     * @param int $courseId
4923
     *
4924
     * @return bool
4925
     */
4926
    public static function categoryIsPublished(CLpCategory $category, $courseId)
4927
    {
4928
        $link = self::getCategoryLinkForTool($category->getId());
4929
        $em = Database::getManager();
4930
4931
        $tools = $em
4932
            ->createQuery("
4933
                SELECT t FROM ChamiloCourseBundle:CTool t
4934
                WHERE t.cId = :course AND
4935
                    t.name = :name AND
4936
                    t.image = 'lp_category.gif' AND
4937
                    t.link LIKE :link
4938
            ")
4939
            ->setParameters([
4940
                'course' => $courseId,
4941
                'name' => strip_tags($category->getName()),
4942
                'link' => "$link%",
4943
            ])
4944
            ->getResult();
4945
4946
        /** @var CTool $tool */
4947
        $tool = current($tools);
4948
4949
        return $tool ? $tool->getVisibility() : false;
4950
    }
4951
4952
    /**
4953
     * Restart the whole learnpath. Return the URL of the first element.
4954
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4955
     * To use a similar method  statically, use the create_new_attempt() method.
4956
     *
4957
     * @return bool
4958
     */
4959
    public function restart()
4960
    {
4961
        if ($this->debug > 0) {
4962
            error_log('In learnpath::restart()', 0);
4963
        }
4964
        // TODO
4965
        // Call autosave method to save the current progress.
4966
        //$this->index = 0;
4967
        if (api_is_invitee()) {
4968
            return false;
4969
        }
4970
        $session_id = api_get_session_id();
4971
        $course_id = api_get_course_int_id();
4972
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4973
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4974
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4975
        if ($this->debug > 2) {
4976
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4977
        }
4978
        Database::query($sql);
4979
        $view_id = Database::insert_id();
4980
4981
        if ($view_id) {
4982
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4983
            Database::query($sql);
4984
            $this->lp_view_id = $view_id;
4985
            $this->attempt = $this->attempt + 1;
4986
        } else {
4987
            $this->error = 'Could not insert into item_view table...';
4988
4989
            return false;
4990
        }
4991
        $this->autocomplete_parents($this->current);
4992
        foreach ($this->items as $index => $dummy) {
4993
            $this->items[$index]->restart();
4994
            $this->items[$index]->set_lp_view($this->lp_view_id);
4995
        }
4996
        $this->first();
4997
4998
        return true;
4999
    }
5000
5001
    /**
5002
     * Saves the current item.
5003
     *
5004
     * @return bool
5005
     */
5006
    public function save_current()
5007
    {
5008
        $debug = $this->debug;
5009
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
5010
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
5011
        if ($debug) {
5012
            error_log('save_current() saving item '.$this->current, 0);
5013
            error_log(''.print_r($this->items, true), 0);
5014
        }
5015
        if (isset($this->items[$this->current]) &&
5016
            is_object($this->items[$this->current])
5017
        ) {
5018
            if ($debug) {
5019
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
5020
            }
5021
5022
            $res = $this->items[$this->current]->save(
5023
                false,
5024
                $this->prerequisites_match($this->current)
5025
            );
5026
            $this->autocomplete_parents($this->current);
5027
            $status = $this->items[$this->current]->get_status();
5028
            $this->update_queue[$this->current] = $status;
5029
5030
            if ($debug) {
5031
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
5032
            }
5033
5034
            return $res;
5035
        }
5036
5037
        return false;
5038
    }
5039
5040
    /**
5041
     * Saves the given item.
5042
     *
5043
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
5044
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
5045
     *
5046
     * @return bool
5047
     */
5048
    public function save_item($item_id = null, $from_outside = true)
5049
    {
5050
        $debug = $this->debug;
5051
        if ($debug) {
5052
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
5053
        }
5054
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
5055
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
5056
        if (empty($item_id)) {
5057
            $item_id = (int) $_REQUEST['id'];
5058
        }
5059
5060
        if (empty($item_id)) {
5061
            $item_id = $this->get_current_item_id();
5062
        }
5063
        if (isset($this->items[$item_id]) &&
5064
            is_object($this->items[$item_id])
5065
        ) {
5066
            // Saving the item.
5067
            $res = $this->items[$item_id]->save(
5068
                $from_outside,
5069
                $this->prerequisites_match($item_id)
5070
            );
5071
5072
            if ($debug) {
5073
                error_log('update_queue before:');
5074
                error_log(print_r($this->update_queue, 1));
5075
            }
5076
            $this->autocomplete_parents($item_id);
5077
5078
            $status = $this->items[$item_id]->get_status();
5079
            $this->update_queue[$item_id] = $status;
5080
5081
            if ($debug) {
5082
                error_log('get_status(): '.$status);
5083
                error_log('update_queue after:');
5084
                error_log(print_r($this->update_queue, 1));
5085
            }
5086
5087
            return $res;
5088
        }
5089
5090
        return false;
5091
    }
5092
5093
    /**
5094
     * Update the last progress only in case.
5095
     */
5096
    public function updateLpProgress()
5097
    {
5098
        $debug = $this->debug;
5099
        if ($debug) {
5100
            error_log('In learnpath::updateLpProgress()', 0);
5101
        }
5102
        $sessionCondition = api_get_session_condition(
5103
            api_get_session_id(),
5104
            true,
5105
            false
5106
        );
5107
        $courseId = api_get_course_int_id();
5108
        $userId = $this->get_user_id();
5109
        $table = Database::get_course_table(TABLE_LP_VIEW);
5110
5111
        [$progress] = $this->get_progress_bar_text('%');
5112
        if ($progress >= 0 && $progress <= 100) {
5113
            // Check database.
5114
            $progress = (int) $progress;
5115
            $sql = "UPDATE $table SET
5116
                            progress = $progress
5117
                        WHERE
5118
                            c_id = $courseId AND
5119
                            lp_id = ".$this->get_id()." AND
5120
                            progress < $progress AND
5121
                            user_id = ".$userId." ".$sessionCondition;
5122
            // Ignore errors as some tables might not have the progress field just yet.
5123
            Database::query($sql);
5124
            if ($debug) {
5125
                error_log($sql);
5126
            }
5127
            $this->progress_db = $progress;
5128
5129
            if (100 == $progress) {
5130
                HookLearningPathEnd::create()
5131
                    ->setEventData(['lp_view_id' => $this->lp_view_id])
5132
                    ->hookLearningPathEnd();
5133
            }
5134
        }
5135
    }
5136
5137
    /**
5138
     * Saves the last item seen's ID only in case.
5139
     */
5140
    public function save_last($score = null)
5141
    {
5142
        $course_id = api_get_course_int_id();
5143
        $debug = $this->debug;
5144
        if ($debug) {
5145
            error_log('In learnpath::save_last()', 0);
5146
        }
5147
        $session_condition = api_get_session_condition(
5148
            api_get_session_id(),
5149
            true,
5150
            false
5151
        );
5152
        $table = Database::get_course_table(TABLE_LP_VIEW);
5153
5154
        $userId = $this->get_user_id();
5155
        if (empty($userId)) {
5156
            $userId = api_get_user_id();
5157
            if ($debug) {
5158
                error_log('$this->get_user_id() was empty, used api_get_user_id() instead in '.__FILE__.' line '.__LINE__);
5159
            }
5160
        }
5161
        if (isset($this->current) && !api_is_invitee()) {
5162
            if ($debug) {
5163
                error_log('Saving current item ('.$this->current.') for later review', 0);
5164
            }
5165
            $sql = "UPDATE $table SET
5166
                        last_item = ".$this->get_current_item_id()."
5167
                    WHERE
5168
                        c_id = $course_id AND
5169
                        lp_id = ".$this->get_id()." AND
5170
                        user_id = ".$userId." ".$session_condition;
5171
            if ($debug) {
5172
                error_log('Saving last item seen : '.$sql, 0);
5173
            }
5174
            Database::query($sql);
5175
        }
5176
5177
        if (!api_is_invitee()) {
5178
            // Save progress.
5179
            [$progress] = $this->get_progress_bar_text('%');
5180
            $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
5181
            $scoreAsProgress = $this->getUseScoreAsProgress();
5182
            if ($scoreAsProgress && $scoreAsProgressSetting && (null === $score || empty($score) || -1 == $score)) {
5183
                if ($debug) {
5184
                    error_log("Return false: Dont save score: $score");
5185
                    error_log("progress: $progress");
5186
                }
5187
5188
                return false;
5189
            }
5190
5191
            if ($scoreAsProgress && $scoreAsProgressSetting) {
5192
                $storedProgress = self::getProgress(
5193
                    $this->get_id(),
5194
                    $userId,
5195
                    $course_id,
5196
                    $this->get_lp_session_id()
5197
                );
5198
5199
                // Check if the stored progress is higher than the new value
5200
                if ($storedProgress >= $progress) {
5201
                    if ($debug) {
5202
                        error_log("Return false: New progress value is lower than stored value - Current value: $storedProgress - New value: $progress [lp ".$this->get_id()." - user ".$userId."]");
5203
                    }
5204
5205
                    return false;
5206
                }
5207
            }
5208
5209
            if ($progress >= 0 && $progress <= 100) {
5210
                // Check database.
5211
                $progress = (int) $progress;
5212
                $sql = "UPDATE $table SET
5213
                            progress = $progress
5214
                        WHERE
5215
                            c_id = $course_id AND
5216
                            lp_id = ".$this->get_id()." AND
5217
                            user_id = ".$userId." ".$session_condition;
5218
                // Ignore errors as some tables might not have the progress field just yet.
5219
                Database::query($sql);
5220
                if ($debug) {
5221
                    error_log($sql);
5222
                }
5223
                $this->progress_db = $progress;
5224
5225
                if (100 == $progress) {
5226
                    HookLearningPathEnd::create()
5227
                        ->setEventData(['lp_view_id' => $this->lp_view_id])
5228
                        ->hookLearningPathEnd();
5229
                }
5230
            }
5231
        }
5232
    }
5233
5234
    /**
5235
     * Sets the current item ID (checks if valid and authorized first).
5236
     *
5237
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
5238
     */
5239
    public function set_current_item($item_id = null)
5240
    {
5241
        $debug = $this->debug;
5242
        if ($debug) {
5243
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
5244
        }
5245
        if (empty($item_id)) {
5246
            if ($debug) {
5247
                error_log('No new current item given, ignore...', 0);
5248
            }
5249
            // Do nothing.
5250
        } else {
5251
            if ($debug) {
5252
                error_log('New current item given is '.$item_id.'...', 0);
5253
            }
5254
            if (is_numeric($item_id)) {
5255
                $item_id = (int) $item_id;
5256
                // TODO: Check in database here.
5257
                $this->last = $this->current;
5258
                $this->current = $item_id;
5259
                // TODO: Update $this->index as well.
5260
                foreach ($this->ordered_items as $index => $item) {
5261
                    if ($item == $this->current) {
5262
                        $this->index = $index;
5263
                        break;
5264
                    }
5265
                }
5266
                if ($debug) {
5267
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
5268
                }
5269
            } else {
5270
                if ($debug) {
5271
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
5272
                }
5273
            }
5274
        }
5275
    }
5276
5277
    /**
5278
     * Sets the encoding.
5279
     *
5280
     * @param string $enc New encoding
5281
     *
5282
     * @return bool
5283
     *
5284
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
5285
     */
5286
    public function set_encoding($enc = 'UTF-8')
5287
    {
5288
        $enc = api_refine_encoding_id($enc);
5289
        if (empty($enc)) {
5290
            $enc = api_get_system_encoding();
5291
        }
5292
        if (api_is_encoding_supported($enc)) {
5293
            $lp = $this->get_id();
5294
            if ($lp != 0) {
5295
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5296
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
5297
                        WHERE iid = ".$lp;
5298
                $res = Database::query($sql);
5299
5300
                return $res;
5301
            }
5302
        }
5303
5304
        return false;
5305
    }
5306
5307
    /**
5308
     * Sets the JS lib setting in the database directly.
5309
     * This is the JavaScript library file this lp needs to load on startup.
5310
     *
5311
     * @param string $lib Proximity setting
5312
     *
5313
     * @return bool True on update success. False otherwise.
5314
     */
5315
    public function set_jslib($lib = '')
5316
    {
5317
        $lp = $this->get_id();
5318
5319
        if ($lp != 0) {
5320
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5321
            $lib = Database::escape_string($lib);
5322
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
5323
                    WHERE iid = $lp";
5324
            $res = Database::query($sql);
5325
5326
            return $res;
5327
        }
5328
5329
        return false;
5330
    }
5331
5332
    /**
5333
     * Sets the name of the LP maker (publisher) (and save).
5334
     *
5335
     * @param string $name Optional string giving the new content_maker of this learnpath
5336
     *
5337
     * @return bool True
5338
     */
5339
    public function set_maker($name = '')
5340
    {
5341
        if (empty($name)) {
5342
            return false;
5343
        }
5344
        $this->maker = $name;
5345
        $table = Database::get_course_table(TABLE_LP_MAIN);
5346
        $lp_id = $this->get_id();
5347
        $sql = "UPDATE $table SET
5348
                content_maker = '".Database::escape_string($this->maker)."'
5349
                WHERE iid = $lp_id";
5350
        Database::query($sql);
5351
5352
        return true;
5353
    }
5354
5355
    /**
5356
     * Sets the name of the current learnpath (and save).
5357
     *
5358
     * @param string $name Optional string giving the new name of this learnpath
5359
     *
5360
     * @return bool True/False
5361
     */
5362
    public function set_name($name = null)
5363
    {
5364
        if (empty($name)) {
5365
            return false;
5366
        }
5367
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5368
        $name = Database::escape_string($name);
5369
5370
        $this->name = $name;
5371
5372
        $lp_id = $this->get_id();
5373
        $course_id = $this->course_info['real_id'];
5374
        $sql = "UPDATE $lp_table SET
5375
                name = '$name'
5376
                WHERE iid = $lp_id";
5377
        $result = Database::query($sql);
5378
        // If the lp is visible on the homepage, change his name there.
5379
        if (Database::affected_rows($result)) {
5380
            $session_id = api_get_session_id();
5381
            $session_condition = api_get_session_condition($session_id);
5382
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
5383
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
5384
            $sql = "UPDATE $tbl_tool SET name = '$name'
5385
            	    WHERE
5386
            	        c_id = $course_id AND
5387
            	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
5388
            Database::query($sql);
5389
5390
            return true;
5391
        }
5392
5393
        return false;
5394
    }
5395
5396
    /**
5397
     * Set index specified prefix terms for all items in this path.
5398
     *
5399
     * @param string $terms_string Comma-separated list of terms
5400
     * @param string $prefix       Xapian term prefix
5401
     *
5402
     * @return bool False on error, true otherwise
5403
     */
5404
    public function set_terms_by_prefix($terms_string, $prefix)
5405
    {
5406
        $course_id = api_get_course_int_id();
5407
        if (api_get_setting('search_enabled') !== 'true') {
5408
            return false;
5409
        }
5410
5411
        if (!extension_loaded('xapian')) {
5412
            return false;
5413
        }
5414
5415
        $terms_string = trim($terms_string);
5416
        $terms = explode(',', $terms_string);
5417
        array_walk($terms, 'trim_value');
5418
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5419
5420
        // Don't do anything if no change, verify only at DB, not the search engine.
5421
        if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
5422
            return false;
5423
        }
5424
5425
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5426
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5427
5428
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5429
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5430
        $lp_id = (int) $_POST['lp_id'];
5431
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5432
        $result = Database::query($sql);
5433
        $di = new ChamiloIndexer();
5434
5435
        while ($lp_item = Database::fetch_array($result)) {
5436
            // Get search_did.
5437
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5438
            $sql = 'SELECT * FROM %s
5439
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5440
                    LIMIT 1';
5441
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5442
5443
            //echo $sql; echo '<br>';
5444
            $res = Database::query($sql);
5445
            if (Database::num_rows($res) > 0) {
5446
                $se_ref = Database::fetch_array($res);
5447
                // Compare terms.
5448
                $doc = $di->get_document($se_ref['search_did']);
5449
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5450
                $xterms = [];
5451
                foreach ($xapian_terms as $xapian_term) {
5452
                    $xterms[] = substr($xapian_term['name'], 1);
5453
                }
5454
5455
                $dterms = $terms;
5456
                $missing_terms = array_diff($dterms, $xterms);
5457
                $deprecated_terms = array_diff($xterms, $dterms);
5458
5459
                // Save it to search engine.
5460
                foreach ($missing_terms as $term) {
5461
                    $doc->add_term($prefix.$term, 1);
5462
                }
5463
                foreach ($deprecated_terms as $term) {
5464
                    $doc->remove_term($prefix.$term);
5465
                }
5466
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5467
                $di->getDb()->flush();
5468
            }
5469
        }
5470
5471
        return true;
5472
    }
5473
5474
    /**
5475
     * Sets the theme of the LP (local/remote) (and save).
5476
     *
5477
     * @param string $name Optional string giving the new theme of this learnpath
5478
     *
5479
     * @return bool Returns true if theme name is not empty
5480
     */
5481
    public function set_theme($name = '')
5482
    {
5483
        $this->theme = $name;
5484
        $table = Database::get_course_table(TABLE_LP_MAIN);
5485
        $lp_id = $this->get_id();
5486
        $sql = "UPDATE $table
5487
                SET theme = '".Database::escape_string($this->theme)."'
5488
                WHERE iid = $lp_id";
5489
        Database::query($sql);
5490
5491
        return true;
5492
    }
5493
5494
    /**
5495
     * Sets the image of an LP (and save).
5496
     *
5497
     * @param string $name Optional string giving the new image of this learnpath
5498
     *
5499
     * @return bool Returns true if theme name is not empty
5500
     */
5501
    public function set_preview_image($name = '')
5502
    {
5503
        $this->preview_image = $name;
5504
        $table = Database::get_course_table(TABLE_LP_MAIN);
5505
        $lp_id = $this->get_id();
5506
        $sql = "UPDATE $table SET
5507
                preview_image = '".Database::escape_string($this->preview_image)."'
5508
                WHERE iid = $lp_id";
5509
        Database::query($sql);
5510
5511
        return true;
5512
    }
5513
5514
    /**
5515
     * Sets the author of a LP (and save).
5516
     *
5517
     * @param string $name Optional string giving the new author of this learnpath
5518
     *
5519
     * @return bool Returns true if author's name is not empty
5520
     */
5521
    public function set_author($name = '')
5522
    {
5523
        $this->author = $name;
5524
        $table = Database::get_course_table(TABLE_LP_MAIN);
5525
        $lp_id = $this->get_id();
5526
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5527
                WHERE iid = $lp_id";
5528
        Database::query($sql);
5529
5530
        return true;
5531
    }
5532
5533
    /**
5534
     * Sets the hide_toc_frame parameter of a LP (and save).
5535
     *
5536
     * @param int $hide 1 if frame is hidden 0 then else
5537
     *
5538
     * @return bool Returns true if author's name is not empty
5539
     */
5540
    public function set_hide_toc_frame($hide)
5541
    {
5542
        if (intval($hide) == $hide) {
5543
            $this->hide_toc_frame = $hide;
5544
            $table = Database::get_course_table(TABLE_LP_MAIN);
5545
            $lp_id = $this->get_id();
5546
            $sql = "UPDATE $table SET
5547
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5548
                    WHERE iid = $lp_id";
5549
            Database::query($sql);
5550
5551
            return true;
5552
        }
5553
5554
        return false;
5555
    }
5556
5557
    /**
5558
     * Sets the prerequisite of a LP (and save).
5559
     *
5560
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5561
     *
5562
     * @return bool returns true if prerequisite is not empty
5563
     */
5564
    public function set_prerequisite($prerequisite)
5565
    {
5566
        $this->prerequisite = (int) $prerequisite;
5567
        $table = Database::get_course_table(TABLE_LP_MAIN);
5568
        $lp_id = $this->get_id();
5569
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5570
                WHERE iid = $lp_id";
5571
        Database::query($sql);
5572
5573
        return true;
5574
    }
5575
5576
    /**
5577
     * Sets the location/proximity of the LP (local/remote) (and save).
5578
     *
5579
     * @param string $name Optional string giving the new location of this learnpath
5580
     *
5581
     * @return bool True on success / False on error
5582
     */
5583
    public function set_proximity($name = '')
5584
    {
5585
        if (empty($name)) {
5586
            return false;
5587
        }
5588
5589
        $this->proximity = $name;
5590
        $table = Database::get_course_table(TABLE_LP_MAIN);
5591
        $lp_id = $this->get_id();
5592
        $sql = "UPDATE $table SET
5593
                    content_local = '".Database::escape_string($name)."'
5594
                WHERE iid = $lp_id";
5595
        Database::query($sql);
5596
5597
        return true;
5598
    }
5599
5600
    /**
5601
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5602
     *
5603
     * @param int $id DB ID of the item
5604
     */
5605
    public function set_previous_item($id)
5606
    {
5607
        if ($this->debug > 0) {
5608
            error_log('In learnpath::set_previous_item()', 0);
5609
        }
5610
        $this->last = $id;
5611
    }
5612
5613
    /**
5614
     * Sets use_max_score.
5615
     *
5616
     * @param int $use_max_score Optional string giving the new location of this learnpath
5617
     *
5618
     * @return bool True on success / False on error
5619
     */
5620
    public function set_use_max_score($use_max_score = 1)
5621
    {
5622
        $use_max_score = (int) $use_max_score;
5623
        $this->use_max_score = $use_max_score;
5624
        $table = Database::get_course_table(TABLE_LP_MAIN);
5625
        $lp_id = $this->get_id();
5626
        $sql = "UPDATE $table SET
5627
                    use_max_score = '".$this->use_max_score."'
5628
                WHERE iid = $lp_id";
5629
        Database::query($sql);
5630
5631
        return true;
5632
    }
5633
5634
    /**
5635
     * Sets and saves the expired_on date.
5636
     *
5637
     * @param string $expired_on Optional string giving the new author of this learnpath
5638
     *
5639
     * @throws \Doctrine\ORM\OptimisticLockException
5640
     *
5641
     * @return bool Returns true if author's name is not empty
5642
     */
5643
    public function set_expired_on($expired_on)
5644
    {
5645
        $em = Database::getManager();
5646
        /** @var CLp $lp */
5647
        $lp = $em
5648
            ->getRepository('ChamiloCourseBundle:CLp')
5649
            ->findOneBy(
5650
                [
5651
                    'iid' => $this->get_id(),
5652
                ]
5653
            );
5654
5655
        if (!$lp) {
5656
            return false;
5657
        }
5658
5659
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5660
5661
        $lp->setExpiredOn($this->expired_on);
5662
        $em->persist($lp);
5663
        $em->flush();
5664
5665
        return true;
5666
    }
5667
5668
    /**
5669
     * Sets and saves the publicated_on date.
5670
     *
5671
     * @param string $publicated_on Optional string giving the new author of this learnpath
5672
     *
5673
     * @throws \Doctrine\ORM\OptimisticLockException
5674
     *
5675
     * @return bool Returns true if author's name is not empty
5676
     */
5677
    public function set_publicated_on($publicated_on)
5678
    {
5679
        $em = Database::getManager();
5680
        /** @var CLp $lp */
5681
        $lp = $em
5682
            ->getRepository('ChamiloCourseBundle:CLp')
5683
            ->findOneBy(
5684
                [
5685
                    'iid' => $this->get_id(),
5686
                ]
5687
            );
5688
5689
        if (!$lp) {
5690
            return false;
5691
        }
5692
5693
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5694
        $lp->setPublicatedOn($this->publicated_on);
5695
        $em->persist($lp);
5696
        $em->flush();
5697
5698
        return true;
5699
    }
5700
5701
    /**
5702
     * Sets and saves the expired_on date.
5703
     *
5704
     * @return bool Returns true if author's name is not empty
5705
     */
5706
    public function set_modified_on()
5707
    {
5708
        $this->modified_on = api_get_utc_datetime();
5709
        $table = Database::get_course_table(TABLE_LP_MAIN);
5710
        $lp_id = $this->get_id();
5711
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5712
                WHERE iid = $lp_id";
5713
        Database::query($sql);
5714
5715
        return true;
5716
    }
5717
5718
    /**
5719
     * Sets the object's error message.
5720
     *
5721
     * @param string $error Error message. If empty, reinits the error string
5722
     */
5723
    public function set_error_msg($error = '')
5724
    {
5725
        if ($this->debug > 0) {
5726
            error_log('In learnpath::set_error_msg()', 0);
5727
        }
5728
        if (empty($error)) {
5729
            $this->error = '';
5730
        } else {
5731
            $this->error .= $error;
5732
        }
5733
    }
5734
5735
    /**
5736
     * Launches the current item if not 'sco'
5737
     * (starts timer and make sure there is a record ready in the DB).
5738
     *
5739
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5740
     *
5741
     * @return bool
5742
     */
5743
    public function start_current_item($allow_new_attempt = false)
5744
    {
5745
        $debug = $this->debug;
5746
        if ($debug) {
5747
            error_log('In learnpath::start_current_item()');
5748
            error_log('current: '.$this->current);
5749
        }
5750
        if ($this->current != 0 && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5751
            $type = $this->get_type();
5752
            $item_type = $this->items[$this->current]->get_type();
5753
            if (($type == 2 && $item_type != 'sco') ||
5754
                ($type == 3 && $item_type != 'au') ||
5755
                (
5756
                    $type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES &&
5757
                    WhispeakAuthPlugin::isAllowedToSaveLpItem($this->current)
5758
                )
5759
            ) {
5760
                if ($debug) {
5761
                    error_log('item type: '.$item_type);
5762
                    error_log('lp type: '.$type);
5763
                }
5764
                $this->items[$this->current]->open($allow_new_attempt);
5765
                $this->autocomplete_parents($this->current);
5766
                $prereq_check = $this->prerequisites_match($this->current);
5767
                if ($debug) {
5768
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5769
                }
5770
5771
                $saveStatus = learnpathItem::isLpItemAutoComplete($this->current);
5772
                if ($saveStatus) {
5773
                    $this->items[$this->current]->save(false, $prereq_check);
5774
                }
5775
            }
5776
            // If sco, then it is supposed to have been updated by some other call.
5777
            if ($item_type === 'sco') {
5778
                $this->items[$this->current]->restart();
5779
            }
5780
        }
5781
        if ($debug) {
5782
            error_log('lp_view_session_id: '.$this->lp_view_session_id);
5783
            error_log('api_get_session_id: '.api_get_session_id());
5784
            error_log('End of learnpath::start_current_item()');
5785
        }
5786
5787
        return true;
5788
    }
5789
5790
    /**
5791
     * Stops the processing and counters for the old item (as held in $this->last).
5792
     *
5793
     * @return bool True/False
5794
     */
5795
    public function stop_previous_item()
5796
    {
5797
        $debug = $this->debug;
5798
        if ($debug) {
5799
            error_log('In learnpath::stop_previous_item()');
5800
        }
5801
5802
        if ($this->last != 0 && $this->last != $this->current &&
5803
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5804
        ) {
5805
            if ($debug) {
5806
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5807
            }
5808
            switch ($this->get_type()) {
5809
                case '3':
5810
                    if ($this->items[$this->last]->get_type() != 'au') {
5811
                        if ($debug) {
5812
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5813
                        }
5814
                        $this->items[$this->last]->close();
5815
                    } else {
5816
                        if ($debug) {
5817
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5818
                        }
5819
                    }
5820
                    break;
5821
                case '2':
5822
                    if ($this->items[$this->last]->get_type() != 'sco') {
5823
                        if ($debug) {
5824
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5825
                        }
5826
                        $this->items[$this->last]->close();
5827
                    } else {
5828
                        if ($debug) {
5829
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5830
                        }
5831
                    }
5832
                    break;
5833
                case '1':
5834
                default:
5835
                    if ($debug) {
5836
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5837
                    }
5838
                    $this->items[$this->last]->close();
5839
                    break;
5840
            }
5841
        } else {
5842
            if ($debug) {
5843
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5844
            }
5845
5846
            return false;
5847
        }
5848
5849
        return true;
5850
    }
5851
5852
    /**
5853
     * Updates the default view mode from fullscreen to embedded and inversely.
5854
     *
5855
     * @return string The current default view mode ('fullscreen' or 'embedded')
5856
     */
5857
    public function update_default_view_mode()
5858
    {
5859
        $table = Database::get_course_table(TABLE_LP_MAIN);
5860
        $sql = "SELECT * FROM $table
5861
                WHERE iid = ".$this->get_id();
5862
        $res = Database::query($sql);
5863
        if (Database::num_rows($res) > 0) {
5864
            $row = Database::fetch_array($res);
5865
            $default_view_mode = $row['default_view_mod'];
5866
            $view_mode = $default_view_mode;
5867
            switch ($default_view_mode) {
5868
                case 'fullscreen': // default with popup
5869
                    $view_mode = 'embedded';
5870
                    break;
5871
                case 'embedded': // default view with left menu
5872
                    $view_mode = 'embedframe';
5873
                    break;
5874
                case 'embedframe': //folded menu
5875
                    $view_mode = 'impress';
5876
                    break;
5877
                case 'impress':
5878
                    $view_mode = 'fullscreen';
5879
                    break;
5880
            }
5881
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5882
                    WHERE iid = ".$this->get_id();
5883
            Database::query($sql);
5884
            $this->mode = $view_mode;
5885
5886
            return $view_mode;
5887
        }
5888
5889
        return -1;
5890
    }
5891
5892
    /**
5893
     * Updates the default behaviour about auto-commiting SCORM updates.
5894
     *
5895
     * @return bool True if auto-commit has been set to 'on', false otherwise
5896
     */
5897
    public function update_default_scorm_commit()
5898
    {
5899
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5900
        $sql = "SELECT * FROM $lp_table
5901
                WHERE iid = ".$this->get_id();
5902
        $res = Database::query($sql);
5903
        if (Database::num_rows($res) > 0) {
5904
            $row = Database::fetch_array($res);
5905
            $force = $row['force_commit'];
5906
            if ($force == 1) {
5907
                $force = 0;
5908
                $force_return = false;
5909
            } elseif ($force == 0) {
5910
                $force = 1;
5911
                $force_return = true;
5912
            }
5913
            $sql = "UPDATE $lp_table SET force_commit = $force
5914
                    WHERE iid = ".$this->get_id();
5915
            Database::query($sql);
5916
            $this->force_commit = $force_return;
5917
5918
            return $force_return;
5919
        }
5920
5921
        return -1;
5922
    }
5923
5924
    /**
5925
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5926
     *
5927
     * @return bool True on success, false on failure
5928
     */
5929
    public function update_display_order()
5930
    {
5931
        $course_id = api_get_course_int_id();
5932
        $table = Database::get_course_table(TABLE_LP_MAIN);
5933
        $sql = "SELECT * FROM $table
5934
                WHERE c_id = $course_id
5935
                ORDER BY display_order";
5936
        $res = Database::query($sql);
5937
        if ($res === false) {
5938
            return false;
5939
        }
5940
5941
        $num = Database::num_rows($res);
5942
        // First check the order is correct, globally (might be wrong because
5943
        // of versions < 1.8.4).
5944
        if ($num > 0) {
5945
            $i = 1;
5946
            while ($row = Database::fetch_array($res)) {
5947
                if ($row['display_order'] != $i) {
5948
                    // If we find a gap in the order, we need to fix it.
5949
                    $sql = "UPDATE $table SET display_order = $i
5950
                            WHERE iid = ".$row['iid'];
5951
                    Database::query($sql);
5952
                }
5953
                $i++;
5954
            }
5955
        }
5956
5957
        return true;
5958
    }
5959
5960
    /**
5961
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5962
     *
5963
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5964
     */
5965
    public function update_reinit()
5966
    {
5967
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5968
        $sql = "SELECT * FROM $lp_table
5969
                WHERE iid = ".$this->get_id();
5970
        $res = Database::query($sql);
5971
        if (Database::num_rows($res) > 0) {
5972
            $row = Database::fetch_array($res);
5973
            $force = $row['prevent_reinit'];
5974
            if ($force == 1) {
5975
                $force = 0;
5976
            } elseif ($force == 0) {
5977
                $force = 1;
5978
            }
5979
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5980
                    WHERE iid = ".$this->get_id();
5981
            Database::query($sql);
5982
            $this->prevent_reinit = $force;
5983
5984
            return $force;
5985
        }
5986
5987
        return -1;
5988
    }
5989
5990
    /**
5991
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5992
     *
5993
     * @return string 'single', 'multi' or 'seriousgame'
5994
     *
5995
     * @author ndiechburg <[email protected]>
5996
     */
5997
    public function get_attempt_mode()
5998
    {
5999
        //Set default value for seriousgame_mode
6000
        if (!isset($this->seriousgame_mode)) {
6001
            $this->seriousgame_mode = 0;
6002
        }
6003
        // Set default value for prevent_reinit
6004
        if (!isset($this->prevent_reinit)) {
6005
            $this->prevent_reinit = 1;
6006
        }
6007
        if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
6008
            return 'seriousgame';
6009
        }
6010
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
6011
            return 'single';
6012
        }
6013
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
6014
            return 'multiple';
6015
        }
6016
6017
        return 'single';
6018
    }
6019
6020
    /**
6021
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
6022
     *
6023
     * @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...
6024
     *
6025
     * @return bool
6026
     *
6027
     * @author ndiechburg <[email protected]>
6028
     */
6029
    public function set_attempt_mode($mode)
6030
    {
6031
        switch ($mode) {
6032
            case 'seriousgame':
6033
                $sg_mode = 1;
6034
                $prevent_reinit = 1;
6035
                break;
6036
            case 'single':
6037
                $sg_mode = 0;
6038
                $prevent_reinit = 1;
6039
                break;
6040
            case 'multiple':
6041
                $sg_mode = 0;
6042
                $prevent_reinit = 0;
6043
                break;
6044
            default:
6045
                $sg_mode = 0;
6046
                $prevent_reinit = 0;
6047
                break;
6048
        }
6049
        $this->prevent_reinit = $prevent_reinit;
6050
        $this->seriousgame_mode = $sg_mode;
6051
        $table = Database::get_course_table(TABLE_LP_MAIN);
6052
        $sql = "UPDATE $table SET
6053
                prevent_reinit = $prevent_reinit ,
6054
                seriousgame_mode = $sg_mode
6055
                WHERE iid = ".$this->get_id();
6056
        $res = Database::query($sql);
6057
        if ($res) {
6058
            return true;
6059
        } else {
6060
            return false;
6061
        }
6062
    }
6063
6064
    /**
6065
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
6066
     *
6067
     * @author ndiechburg <[email protected]>
6068
     */
6069
    public function switch_attempt_mode()
6070
    {
6071
        $mode = $this->get_attempt_mode();
6072
        switch ($mode) {
6073
            case 'single':
6074
                $next_mode = 'multiple';
6075
                break;
6076
            case 'multiple':
6077
                $next_mode = 'seriousgame';
6078
                break;
6079
            case 'seriousgame':
6080
            default:
6081
                $next_mode = 'single';
6082
                break;
6083
        }
6084
        $this->set_attempt_mode($next_mode);
6085
    }
6086
6087
    /**
6088
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
6089
     * but possibility to do again a completed item.
6090
     *
6091
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
6092
     *
6093
     * @author ndiechburg <[email protected]>
6094
     */
6095
    public function set_seriousgame_mode()
6096
    {
6097
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
6098
        $sql = "SELECT * FROM $lp_table
6099
                WHERE iid = ".$this->get_id();
6100
        $res = Database::query($sql);
6101
        if (Database::num_rows($res) > 0) {
6102
            $row = Database::fetch_array($res);
6103
            $force = $row['seriousgame_mode'];
6104
            if ($force == 1) {
6105
                $force = 0;
6106
            } elseif ($force == 0) {
6107
                $force = 1;
6108
            }
6109
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
6110
			        WHERE iid = ".$this->get_id();
6111
            Database::query($sql);
6112
            $this->seriousgame_mode = $force;
6113
6114
            return $force;
6115
        }
6116
6117
        return -1;
6118
    }
6119
6120
    /**
6121
     * Updates the "scorm_debug" value that shows or hide the debug window.
6122
     *
6123
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
6124
     */
6125
    public function update_scorm_debug()
6126
    {
6127
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
6128
        $sql = "SELECT * FROM $lp_table
6129
                WHERE iid = ".$this->get_id();
6130
        $res = Database::query($sql);
6131
        if (Database::num_rows($res) > 0) {
6132
            $row = Database::fetch_array($res);
6133
            $force = $row['debug'];
6134
            if ($force == 1) {
6135
                $force = 0;
6136
            } elseif ($force == 0) {
6137
                $force = 1;
6138
            }
6139
            $sql = "UPDATE $lp_table SET debug = $force
6140
                    WHERE iid = ".$this->get_id();
6141
            Database::query($sql);
6142
            $this->scorm_debug = $force;
6143
6144
            return $force;
6145
        }
6146
6147
        return -1;
6148
    }
6149
6150
    /**
6151
     * Function that makes a call to the function sort_tree_array and create_tree_array.
6152
     *
6153
     * @author Kevin Van Den Haute
6154
     *
6155
     * @param  array
6156
     */
6157
    public function tree_array($array)
6158
    {
6159
        $array = $this->sort_tree_array($array);
6160
        $this->create_tree_array($array);
6161
    }
6162
6163
    /**
6164
     * Creates an array with the elements of the learning path tree in it.
6165
     *
6166
     * @author Kevin Van Den Haute
6167
     *
6168
     * @param array $array
6169
     * @param int   $parent
6170
     * @param int   $depth
6171
     * @param array $tmp
6172
     */
6173
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
6174
    {
6175
        if (is_array($array)) {
6176
            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...
6177
                if ($array[$i]['parent_item_id'] == $parent) {
6178
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
6179
                        $tmp[] = $array[$i]['parent_item_id'];
6180
                        $depth++;
6181
                    }
6182
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
6183
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
6184
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
6185
6186
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
6187
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
6188
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
6189
                    $this->arrMenu[] = [
6190
                        'id' => $array[$i]['id'],
6191
                        'ref' => $ref,
6192
                        'item_type' => $array[$i]['item_type'],
6193
                        'title' => $array[$i]['title'],
6194
                        'title_raw' => $array[$i]['title_raw'],
6195
                        'path' => $path,
6196
                        'description' => $array[$i]['description'],
6197
                        'parent_item_id' => $array[$i]['parent_item_id'],
6198
                        'previous_item_id' => $array[$i]['previous_item_id'],
6199
                        'next_item_id' => $array[$i]['next_item_id'],
6200
                        'min_score' => $array[$i]['min_score'],
6201
                        'max_score' => $array[$i]['max_score'],
6202
                        'mastery_score' => $array[$i]['mastery_score'],
6203
                        'display_order' => $array[$i]['display_order'],
6204
                        'prerequisite' => $preq,
6205
                        'depth' => $depth,
6206
                        'audio' => $audio,
6207
                        'prerequisite_min_score' => $prerequisiteMinScore,
6208
                        'prerequisite_max_score' => $prerequisiteMaxScore,
6209
                    ];
6210
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
6211
                }
6212
            }
6213
        }
6214
    }
6215
6216
    /**
6217
     * Sorts a multi dimensional array by parent id and display order.
6218
     *
6219
     * @author Kevin Van Den Haute
6220
     *
6221
     * @param array $array (array with al the learning path items in it)
6222
     *
6223
     * @return array
6224
     */
6225
    public function sort_tree_array($array)
6226
    {
6227
        foreach ($array as $key => $row) {
6228
            $parent[$key] = $row['parent_item_id'];
6229
            $position[$key] = $row['display_order'];
6230
        }
6231
6232
        if (count($array) > 0) {
6233
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
6234
        }
6235
6236
        return $array;
6237
    }
6238
6239
    /**
6240
     * Function that creates a html list of learning path items so that we can add audio files to them.
6241
     *
6242
     * @author Kevin Van Den Haute
6243
     *
6244
     * @return string
6245
     */
6246
    public function overview()
6247
    {
6248
        $return = '';
6249
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
6250
6251
        // we need to start a form when we want to update all the mp3 files
6252
        if ($update_audio == 'true') {
6253
            $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">';
6254
        }
6255
        $return .= '<div id="message"></div>';
6256
        if (count($this->items) == 0) {
6257
            $return .= Display::return_message(get_lang('YouShouldAddItemsBeforeAttachAudio'), 'normal');
6258
        } else {
6259
            $return_audio = '<table class="table table-hover table-striped data_table">';
6260
            $return_audio .= '<tr>';
6261
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
6262
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
6263
            $return_audio .= '</tr>';
6264
6265
            if ($update_audio != 'true') {
6266
                $return .= '<div class="col-md-12">';
6267
                $return .= self::return_new_tree($update_audio);
6268
                $return .= '</div>';
6269
                $return .= Display::div(
6270
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
6271
                    ['style' => 'float:left; margin-top:15px;width:100%']
6272
                );
6273
            } else {
6274
                $return_audio .= self::return_new_tree($update_audio);
6275
                $return .= $return_audio.'</table>';
6276
            }
6277
6278
            // We need to close the form when we are updating the mp3 files.
6279
            if ($update_audio == 'true') {
6280
                $return .= '<div class="footer-audio">';
6281
                $return .= Display::button(
6282
                    'save_audio',
6283
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('SaveAudioAndOrganization'),
6284
                    ['class' => 'btn btn-primary', 'type' => 'submit']
6285
                );
6286
                $return .= '</div>';
6287
            }
6288
        }
6289
6290
        // We need to close the form when we are updating the mp3 files.
6291
        if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
6292
            $return .= '</form>';
6293
        }
6294
6295
        return $return;
6296
    }
6297
6298
    /**
6299
     * @param string $update_audio
6300
     *
6301
     * @return array
6302
     */
6303
    public function processBuildMenuElements($update_audio = 'false')
6304
    {
6305
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
6306
        $arrLP = $this->getItemsForForm();
6307
6308
        $this->tree_array($arrLP);
6309
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
6310
        unset($this->arrMenu);
6311
        $default_data = null;
6312
        $default_content = null;
6313
        $elements = [];
6314
        $return_audio = null;
6315
        $iconPath = api_get_path(SYS_CODE_PATH).'img/';
6316
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
6317
        $countItems = count($arrLP);
6318
6319
        $upIcon = Display::return_icon(
6320
            'up.png',
6321
            get_lang('Up'),
6322
            [],
6323
            ICON_SIZE_TINY
6324
        );
6325
6326
        $disableUpIcon = Display::return_icon(
6327
            'up_na.png',
6328
            get_lang('Up'),
6329
            [],
6330
            ICON_SIZE_TINY
6331
        );
6332
6333
        $downIcon = Display::return_icon(
6334
            'down.png',
6335
            get_lang('Down'),
6336
            [],
6337
            ICON_SIZE_TINY
6338
        );
6339
6340
        $disableDownIcon = Display::return_icon(
6341
            'down_na.png',
6342
            get_lang('Down'),
6343
            [],
6344
            ICON_SIZE_TINY
6345
        );
6346
6347
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
6348
6349
        $pluginCalendar = api_get_plugin_setting('learning_calendar', 'enabled') === 'true';
6350
        $plugin = null;
6351
        if ($pluginCalendar) {
6352
            $plugin = LearningCalendarPlugin::create();
6353
        }
6354
6355
        for ($i = 0; $i < $countItems; $i++) {
6356
            $parent_id = $arrLP[$i]['parent_item_id'];
6357
            $title = $arrLP[$i]['title'];
6358
            $title_cut = $arrLP[$i]['title_raw'];
6359
            if ($show === false) {
6360
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
6361
            }
6362
            // Link for the documents
6363
            if ($arrLP[$i]['item_type'] === 'document' || $arrLP[$i]['item_type'] == TOOL_READOUT_TEXT) {
6364
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6365
                $title_cut = Display::url(
6366
                    $title_cut,
6367
                    $url,
6368
                    [
6369
                        'class' => 'ajax moved',
6370
                        'data-title' => $title,
6371
                        'title' => $title,
6372
                    ]
6373
                );
6374
            }
6375
6376
            // Detect if type is FINAL_ITEM to set path_id to SESSION
6377
            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6378
                Session::write('pathItem', $arrLP[$i]['path']);
6379
            }
6380
6381
            $oddClass = 'row_even';
6382
            if (($i % 2) == 0) {
6383
                $oddClass = 'row_odd';
6384
            }
6385
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
6386
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
6387
6388
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
6389
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
6390
            } else {
6391
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
6392
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
6393
                } else {
6394
                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
6395
                        $icon = Display::return_icon('flag_checkered.png');
6396
                    } elseif (TOOL_XAPI === $arrLP[$i]['item_type']) {
6397
                        $icon = Display::return_icon('import_scorm.png');
6398
                    } elseif (TOOL_H5P) {
6399
                        $icon = Display::return_icon('plugin_h5p_import.png');
6400
                    } elseif (TOOL_SURVEY === $arrLP[$i]['item_type']) {
6401
                        $icon = Display::return_icon('survey.gif');
6402
                    } else {
6403
                        $icon = Display::return_icon('folder_document.gif');
6404
                    }
6405
                }
6406
            }
6407
6408
            // The audio column.
6409
            $return_audio .= '<td align="left" style="padding-left:10px;">';
6410
            $audio = '';
6411
            if (!$update_audio || $update_audio != 'true') {
6412
                if (empty($arrLP[$i]['audio'])) {
6413
                    $audio .= '';
6414
                }
6415
            } else {
6416
                $types = self::getChapterTypes();
6417
                if (!in_array($arrLP[$i]['item_type'], $types)) {
6418
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
6419
                    if (!empty($arrLP[$i]['audio'])) {
6420
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
6421
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('RemoveAudio');
6422
                    }
6423
                }
6424
            }
6425
6426
            $return_audio .= Display::span($icon.' '.$title).
6427
                Display::tag(
6428
                    'td',
6429
                    $audio,
6430
                    ['style' => '']
6431
                );
6432
            $return_audio .= '</td>';
6433
            $move_icon = '';
6434
            $move_item_icon = '';
6435
            $edit_icon = '';
6436
            $delete_icon = '';
6437
            $audio_icon = '';
6438
            $prerequisities_icon = '';
6439
            $forumIcon = '';
6440
            $previewIcon = '';
6441
            $pluginCalendarIcon = '';
6442
            $orderIcons = '';
6443
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
6444
6445
            if ($is_allowed_to_edit) {
6446
                if (!$update_audio || $update_audio != 'true') {
6447
                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
6448
                        $move_icon .= '<a class="moved" href="#">';
6449
                        $move_icon .= Display::return_icon(
6450
                            'move_everywhere.png',
6451
                            get_lang('Move'),
6452
                            [],
6453
                            ICON_SIZE_TINY
6454
                        );
6455
                        $move_icon .= '</a>';
6456
                    }
6457
                }
6458
6459
                // No edit for this item types
6460
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6461
                    if ($arrLP[$i]['item_type'] != 'dir') {
6462
                        $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">';
6463
                        $edit_icon .= Display::return_icon(
6464
                            'edit.png',
6465
                            get_lang('LearnpathEditModule'),
6466
                            [],
6467
                            ICON_SIZE_TINY
6468
                        );
6469
                        $edit_icon .= '</a>';
6470
6471
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6472
                            $forumThread = null;
6473
                            if (isset($this->items[$arrLP[$i]['id']])) {
6474
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6475
                                    $this->course_int_id,
6476
                                    $this->lp_session_id
6477
                                );
6478
                            }
6479
                            if ($forumThread) {
6480
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6481
                                        'action' => 'dissociate_forum',
6482
                                        'id' => $arrLP[$i]['id'],
6483
                                        'lp_id' => $this->lp_id,
6484
                                    ]);
6485
                                $forumIcon = Display::url(
6486
                                    Display::return_icon(
6487
                                        'forum.png',
6488
                                        get_lang('DissociateForumToLPItem'),
6489
                                        [],
6490
                                        ICON_SIZE_TINY
6491
                                    ),
6492
                                    $forumIconUrl,
6493
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6494
                                );
6495
                            } else {
6496
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6497
                                        'action' => 'create_forum',
6498
                                        'id' => $arrLP[$i]['id'],
6499
                                        'lp_id' => $this->lp_id,
6500
                                    ]);
6501
                                $forumIcon = Display::url(
6502
                                    Display::return_icon(
6503
                                        'forum.png',
6504
                                        get_lang('AssociateForumToLPItem'),
6505
                                        [],
6506
                                        ICON_SIZE_TINY
6507
                                    ),
6508
                                    $forumIconUrl,
6509
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6510
                                );
6511
                            }
6512
                        }
6513
                    } else {
6514
                        $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">';
6515
                        $edit_icon .= Display::return_icon(
6516
                            'edit.png',
6517
                            get_lang('LearnpathEditModule'),
6518
                            [],
6519
                            ICON_SIZE_TINY
6520
                        );
6521
                        $edit_icon .= '</a>';
6522
                    }
6523
                } else {
6524
                    if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6525
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6526
                        $edit_icon .= Display::return_icon(
6527
                            'edit.png',
6528
                            get_lang('Edit'),
6529
                            [],
6530
                            ICON_SIZE_TINY
6531
                        );
6532
                        $edit_icon .= '</a>';
6533
                    }
6534
                }
6535
6536
                if ($pluginCalendar) {
6537
                    $pluginLink = $pluginUrl.
6538
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6539
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6540
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6541
                    if ($itemInfo && $itemInfo['value'] == 1) {
6542
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6543
                    }
6544
                    $pluginCalendarIcon = Display::url(
6545
                        $iconCalendar,
6546
                        $pluginLink,
6547
                        ['class' => 'btn btn-default']
6548
                    );
6549
                }
6550
6551
                if ($arrLP[$i]['item_type'] != 'final_item') {
6552
                    $orderIcons = Display::url(
6553
                        $upIcon,
6554
                        'javascript:void(0)',
6555
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
6556
                    );
6557
                    $orderIcons .= Display::url(
6558
                        $downIcon,
6559
                        'javascript:void(0)',
6560
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
6561
                    );
6562
                }
6563
6564
                $delete_icon .= ' <a
6565
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
6566
                    onclick="return confirmation(\''.addslashes($title).'\');"
6567
                    class="btn btn-default">';
6568
                $delete_icon .= Display::return_icon(
6569
                    'delete.png',
6570
                    get_lang('LearnpathDeleteModule'),
6571
                    [],
6572
                    ICON_SIZE_TINY
6573
                );
6574
                $delete_icon .= '</a>';
6575
6576
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6577
                $previewImage = Display::return_icon(
6578
                    'preview_view.png',
6579
                    get_lang('Preview'),
6580
                    [],
6581
                    ICON_SIZE_TINY
6582
                );
6583
6584
                switch ($arrLP[$i]['item_type']) {
6585
                    case TOOL_DOCUMENT:
6586
                    case TOOL_LP_FINAL_ITEM:
6587
                    case TOOL_READOUT_TEXT:
6588
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6589
                        $previewIcon = Display::url(
6590
                            $previewImage,
6591
                            $urlPreviewLink,
6592
                            [
6593
                                'target' => '_blank',
6594
                                'class' => 'btn btn-default',
6595
                                'data-title' => $arrLP[$i]['title'],
6596
                                'title' => $arrLP[$i]['title'],
6597
                            ]
6598
                        );
6599
                        break;
6600
                    case TOOL_THREAD:
6601
                    case TOOL_FORUM:
6602
                    case TOOL_QUIZ:
6603
                    case TOOL_STUDENTPUBLICATION:
6604
                    case TOOL_LP_FINAL_ITEM:
6605
                    case TOOL_LINK:
6606
                        $class = 'btn btn-default';
6607
                        $target = '_blank';
6608
                        $link = self::rl_get_resource_link_for_learnpath(
6609
                            $this->course_int_id,
6610
                            $this->lp_id,
6611
                            $arrLP[$i]['id'],
6612
                            0
6613
                        );
6614
                        $previewIcon = Display::url(
6615
                            $previewImage,
6616
                            $link,
6617
                            [
6618
                                'class' => $class,
6619
                                'data-title' => $arrLP[$i]['title'],
6620
                                'title' => $arrLP[$i]['title'],
6621
                                'target' => $target,
6622
                            ]
6623
                        );
6624
                        break;
6625
                    default:
6626
                        $previewIcon = Display::url(
6627
                            $previewImage,
6628
                            $url.'&action=view_item',
6629
                            ['class' => 'btn btn-default', 'target' => '_blank']
6630
                        );
6631
                        break;
6632
                }
6633
6634
                if ($arrLP[$i]['item_type'] != 'dir') {
6635
                    $prerequisities_icon = Display::url(
6636
                        Display::return_icon(
6637
                            'accept.png',
6638
                            get_lang('LearnpathPrerequisites'),
6639
                            [],
6640
                            ICON_SIZE_TINY
6641
                        ),
6642
                        $url.'&action=edit_item_prereq',
6643
                        ['class' => 'btn btn-default']
6644
                    );
6645
                    if ($arrLP[$i]['item_type'] != 'final_item') {
6646
                        $move_item_icon = Display::url(
6647
                            Display::return_icon(
6648
                                'move.png',
6649
                                get_lang('Move'),
6650
                                [],
6651
                                ICON_SIZE_TINY
6652
                            ),
6653
                            $url.'&action=move_item',
6654
                            ['class' => 'btn btn-default']
6655
                        );
6656
                    }
6657
                    $audio_icon = Display::url(
6658
                        Display::return_icon(
6659
                            'audio.png',
6660
                            get_lang('UplUpload'),
6661
                            [],
6662
                            ICON_SIZE_TINY
6663
                        ),
6664
                        $url.'&action=add_audio',
6665
                        ['class' => 'btn btn-default']
6666
                    );
6667
                }
6668
            }
6669
            if ($update_audio != 'true') {
6670
                $row = $move_icon.' '.$icon.
6671
                    Display::span($title_cut).
6672
                    Display::tag(
6673
                        'div',
6674
                        "<div class=\"btn-group btn-group-xs\">
6675
                                    $previewIcon
6676
                                    $audio
6677
                                    $edit_icon
6678
                                    $pluginCalendarIcon
6679
                                    $forumIcon
6680
                                    $prerequisities_icon
6681
                                    $move_item_icon
6682
                                    $audio_icon
6683
                                    $orderIcons
6684
                                    $delete_icon
6685
                                </div>",
6686
                        ['class' => 'btn-toolbar button_actions']
6687
                    );
6688
            } else {
6689
                $row =
6690
                    Display::span($title.$icon).
6691
                    Display::span($audio, ['class' => 'button_actions']);
6692
            }
6693
6694
            $default_data[$arrLP[$i]['id']] = $row;
6695
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6696
6697
            if (empty($parent_id)) {
6698
                $elements[$arrLP[$i]['id']]['data'] = $row;
6699
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6700
            } else {
6701
                $parent_arrays = [];
6702
                if ($arrLP[$i]['depth'] > 1) {
6703
                    // Getting list of parents
6704
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6705
                        foreach ($arrLP as $item) {
6706
                            if ($item['id'] == $parent_id) {
6707
                                if ($item['parent_item_id'] == 0) {
6708
                                    $parent_id = $item['id'];
6709
                                    break;
6710
                                } else {
6711
                                    $parent_id = $item['parent_item_id'];
6712
                                    if (empty($parent_arrays)) {
6713
                                        $parent_arrays[] = intval($item['id']);
6714
                                    }
6715
                                    $parent_arrays[] = $parent_id;
6716
                                    break;
6717
                                }
6718
                            }
6719
                        }
6720
                    }
6721
                }
6722
6723
                if (!empty($parent_arrays)) {
6724
                    $parent_arrays = array_reverse($parent_arrays);
6725
                    $val = '$elements';
6726
                    $x = 0;
6727
                    foreach ($parent_arrays as $item) {
6728
                        if ($x != count($parent_arrays) - 1) {
6729
                            $val .= '["'.$item.'"]["children"]';
6730
                        } else {
6731
                            $val .= '["'.$item.'"]["children"]';
6732
                        }
6733
                        $x++;
6734
                    }
6735
                    $val .= "";
6736
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6737
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6738
                } else {
6739
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6740
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6741
                }
6742
            }
6743
        }
6744
6745
        return [
6746
            'elements' => $elements,
6747
            'default_data' => $default_data,
6748
            'default_content' => $default_content,
6749
            'return_audio' => $return_audio,
6750
        ];
6751
    }
6752
6753
    /**
6754
     * @param string $updateAudio true/false strings
6755
     *
6756
     * @return string
6757
     */
6758
    public function returnLpItemList($updateAudio)
6759
    {
6760
        $result = $this->processBuildMenuElements($updateAudio);
6761
6762
        $html = self::print_recursive(
6763
            $result['elements'],
6764
            $result['default_data'],
6765
            $result['default_content']
6766
        );
6767
6768
        if (!empty($html)) {
6769
            $html .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6770
        }
6771
6772
        return $html;
6773
    }
6774
6775
    /**
6776
     * @param string $update_audio
6777
     * @param bool   $drop_element_here
6778
     *
6779
     * @return string
6780
     */
6781
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6782
    {
6783
        $result = $this->processBuildMenuElements($update_audio);
6784
6785
        $list = '<ul id="lp_item_list">';
6786
        $tree = $this->print_recursive(
6787
            $result['elements'],
6788
            $result['default_data'],
6789
            $result['default_content']
6790
        );
6791
6792
        if (!empty($tree)) {
6793
            $list .= $tree;
6794
        } else {
6795
            if ($drop_element_here) {
6796
                $list .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6797
            }
6798
        }
6799
        $list .= '</ul>';
6800
6801
        $return = Display::panelCollapse(
6802
            $this->getNameNoTags(),
6803
            $list,
6804
            'scorm-list',
6805
            null,
6806
            'scorm-list-accordion',
6807
            'scorm-list-collapse'
6808
        );
6809
6810
        if ($update_audio === 'true') {
6811
            $return = $result['return_audio'];
6812
        }
6813
6814
        return $return;
6815
    }
6816
6817
    /**
6818
     * @param array $elements
6819
     * @param array $default_data
6820
     * @param array $default_content
6821
     *
6822
     * @return string
6823
     */
6824
    public function print_recursive($elements, $default_data, $default_content)
6825
    {
6826
        $return = '';
6827
        foreach ($elements as $key => $item) {
6828
            if (TOOL_LP_FINAL_ITEM === $item['type']) {
6829
                $key = 'final_item';
6830
            }
6831
            if (isset($item['load_data']) || empty($item['data'])) {
6832
                $item['data'] = $default_data[$item['load_data']];
6833
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6834
            }
6835
            $sub_list = '';
6836
            if (isset($item['type']) && $item['type'] === 'dir') {
6837
                // empty value
6838
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6839
            }
6840
            if (empty($item['children'])) {
6841
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6842
                $active = null;
6843
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6844
                    $active = 'active';
6845
                }
6846
                $return .= Display::tag(
6847
                    'li',
6848
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6849
                    ['id' => $key, 'class' => 'record li_container']
6850
                );
6851
            } else {
6852
                // Sections
6853
                $data = '';
6854
                if (isset($item['children'])) {
6855
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6856
                }
6857
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6858
                $return .= Display::tag(
6859
                    'li',
6860
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6861
                    ['id' => $key, 'class' => 'record li_container']
6862
                );
6863
            }
6864
        }
6865
6866
        return $return;
6867
    }
6868
6869
    /**
6870
     * This function builds the action menu.
6871
     *
6872
     * @param bool   $returnString           Optional
6873
     * @param bool   $showRequirementButtons Optional. Allow show the requirements button
6874
     * @param bool   $isConfigPage           Optional. If is the config page, show the edit button
6875
     * @param bool   $allowExpand            Optional. Allow show the expand/contract button
6876
     * @param string $action
6877
     * @param array  $extraField
6878
     *
6879
     * @return string
6880
     */
6881
    public function build_action_menu(
6882
        $returnString = false,
6883
        $showRequirementButtons = true,
6884
        $isConfigPage = false,
6885
        $allowExpand = true,
6886
        $action = '',
6887
        $extraField = []
6888
    ) {
6889
        $actionsRight = '';
6890
        $lpId = $this->lp_id;
6891
        if (!isset($extraField['backTo']) && empty($extraField['backTo'])) {
6892
            $back = Display::url(
6893
                Display::return_icon(
6894
                    'back.png',
6895
                    get_lang('ReturnToLearningPaths'),
6896
                    '',
6897
                    ICON_SIZE_MEDIUM
6898
                ),
6899
                'lp_controller.php?'.api_get_cidreq()
6900
            );
6901
        } else {
6902
            $back = Display::url(
6903
                Display::return_icon(
6904
                    'back.png',
6905
                    get_lang('Back'),
6906
                    '',
6907
                    ICON_SIZE_MEDIUM
6908
                ),
6909
                $extraField['backTo']
6910
            );
6911
        }
6912
6913
        $actionsLeft = $back;
6914
        $actionsLeft .= Display::url(
6915
            Display::return_icon(
6916
                'preview_view.png',
6917
                get_lang('Preview'),
6918
                '',
6919
                ICON_SIZE_MEDIUM
6920
            ),
6921
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6922
                'action' => 'view',
6923
                'lp_id' => $lpId,
6924
                'isStudentView' => 'true',
6925
            ])
6926
        );
6927
6928
        $actionsLeft .= Display::url(
6929
            Display::return_icon(
6930
                'upload_audio.png',
6931
                get_lang('UpdateAllAudioFragments'),
6932
                '',
6933
                ICON_SIZE_MEDIUM
6934
            ),
6935
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6936
                'action' => 'admin_view',
6937
                'lp_id' => $lpId,
6938
                'updateaudio' => 'true',
6939
            ])
6940
        );
6941
6942
        $subscriptionSettings = self::getSubscriptionSettings();
6943
        $request = api_request_uri();
6944
        if (strpos($request, 'edit') === false) {
6945
            $actionsLeft .= Display::url(
6946
                Display::return_icon(
6947
                    'settings.png',
6948
                    get_lang('CourseSettings'),
6949
                    '',
6950
                    ICON_SIZE_MEDIUM
6951
                ),
6952
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6953
                    'action' => 'edit',
6954
                    'lp_id' => $lpId,
6955
                ])
6956
            );
6957
        }
6958
6959
        if ((strpos($request, 'build') === false &&
6960
            strpos($request, 'add_item') === false) ||
6961
            in_array($action, ['add_audio'])
6962
        ) {
6963
            $actionsLeft .= Display::url(
6964
                Display::return_icon(
6965
                    'edit.png',
6966
                    get_lang('Edit'),
6967
                    '',
6968
                    ICON_SIZE_MEDIUM
6969
                ),
6970
                'lp_controller.php?'.http_build_query([
6971
                    'action' => 'build',
6972
                    'lp_id' => $lpId,
6973
                ]).'&'.api_get_cidreq()
6974
            );
6975
        }
6976
6977
        if (strpos(api_get_self(), 'lp_subscribe_users.php') === false) {
6978
            if ($this->subscribeUsers == 1 &&
6979
                $subscriptionSettings['allow_add_users_to_lp']) {
6980
                $actionsLeft .= Display::url(
6981
                    Display::return_icon(
6982
                        'user.png',
6983
                        get_lang('SubscribeUsersToLp'),
6984
                        '',
6985
                        ICON_SIZE_MEDIUM
6986
                    ),
6987
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=".$lpId."&".api_get_cidreq()
6988
                );
6989
            }
6990
        }
6991
6992
        if ($allowExpand) {
6993
            $actionsLeft .= Display::url(
6994
                Display::return_icon(
6995
                    'expand.png',
6996
                    get_lang('Expand'),
6997
                    ['id' => 'expand'],
6998
                    ICON_SIZE_MEDIUM
6999
                ).
7000
                Display::return_icon(
7001
                    'contract.png',
7002
                    get_lang('Collapse'),
7003
                    ['id' => 'contract', 'class' => 'hide'],
7004
                    ICON_SIZE_MEDIUM
7005
                ),
7006
                '#',
7007
                ['role' => 'button', 'id' => 'hide_bar_template']
7008
            );
7009
        }
7010
7011
        if ($showRequirementButtons) {
7012
            $buttons = [
7013
                [
7014
                    'title' => get_lang('SetPrerequisiteForEachItem'),
7015
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
7016
                        'action' => 'set_previous_step_as_prerequisite',
7017
                        'lp_id' => $lpId,
7018
                    ]),
7019
                ],
7020
                [
7021
                    'title' => get_lang('ClearAllPrerequisites'),
7022
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
7023
                        'action' => 'clear_prerequisites',
7024
                        'lp_id' => $lpId,
7025
                    ]),
7026
                ],
7027
            ];
7028
            $actionsRight = Display::groupButtonWithDropDown(
7029
                get_lang('PrerequisitesOptions'),
7030
                $buttons,
7031
                true
7032
            );
7033
        }
7034
7035
        if (api_is_platform_admin() && isset($extraField['authorlp'])) {
7036
            $actionsLeft .= Display::url(
7037
                Display::return_icon(
7038
                    'add-groups.png',
7039
                    get_lang('Author'),
7040
                    '',
7041
                    ICON_SIZE_MEDIUM
7042
                ),
7043
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
7044
                    'action' => 'author_view',
7045
                    'lp_id' => $lpId,
7046
                ])
7047
            );
7048
        }
7049
7050
        $toolbar = Display::toolbarAction(
7051
            'actions-lp-controller',
7052
            [$actionsLeft, $actionsRight]
7053
        );
7054
7055
        if ($returnString) {
7056
            return $toolbar;
7057
        }
7058
7059
        echo $toolbar;
7060
    }
7061
7062
    /**
7063
     * Creates the default learning path folder.
7064
     *
7065
     * @param array $course
7066
     * @param int   $creatorId
7067
     *
7068
     * @return bool
7069
     */
7070
    public static function generate_learning_path_folder($course, $creatorId = 0)
7071
    {
7072
        // Creating learning_path folder
7073
        $dir = '/learning_path';
7074
        $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
7075
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
7076
7077
        $folder = false;
7078
        if (!is_dir($filepath.'/'.$dir)) {
7079
            $folderData = create_unexisting_directory(
7080
                $course,
7081
                $creatorId,
7082
                0,
7083
                null,
7084
                0,
7085
                $filepath,
7086
                $dir,
7087
                get_lang('LearningPaths'),
7088
                0
7089
            );
7090
            if (!empty($folderData)) {
7091
                $folder = true;
7092
            }
7093
        } else {
7094
            $folder = true;
7095
        }
7096
7097
        return $folder;
7098
    }
7099
7100
    /**
7101
     * @param array  $course
7102
     * @param string $lp_name
7103
     * @param int    $creatorId
7104
     *
7105
     * @return array
7106
     */
7107
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
7108
    {
7109
        $filepath = '';
7110
        $dir = '/learning_path/';
7111
7112
        if (empty($lp_name)) {
7113
            $lp_name = $this->name;
7114
        }
7115
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
7116
        $folder = self::generate_learning_path_folder($course, $creatorId);
7117
7118
        // Limits title size
7119
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
7120
        $dir = $dir.$title;
7121
7122
        // Creating LP folder
7123
        $documentId = null;
7124
        if ($folder) {
7125
            $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
7126
            if (!is_dir($filepath.'/'.$dir)) {
7127
                $folderData = create_unexisting_directory(
7128
                    $course,
7129
                    $creatorId,
7130
                    0,
7131
                    0,
7132
                    0,
7133
                    $filepath,
7134
                    $dir,
7135
                    $lp_name
7136
                );
7137
                if (!empty($folderData)) {
7138
                    $folder = true;
7139
                }
7140
7141
                $documentId = $folderData['id'];
7142
            } else {
7143
                $folder = true;
7144
            }
7145
            $dir = $dir.'/';
7146
            if ($folder) {
7147
                $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
7148
            }
7149
        }
7150
7151
        if (empty($documentId)) {
7152
            $dir = api_remove_trailing_slash($dir);
7153
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
7154
        }
7155
7156
        $array = [
7157
            'dir' => $dir,
7158
            'filepath' => $filepath,
7159
            'folder' => $folder,
7160
            'id' => $documentId,
7161
        ];
7162
7163
        return $array;
7164
    }
7165
7166
    /**
7167
     * Create a new document //still needs some finetuning.
7168
     *
7169
     * @param array  $courseInfo
7170
     * @param string $content
7171
     * @param string $title
7172
     * @param string $extension
7173
     * @param int    $parentId
7174
     * @param int    $creatorId  creator id
7175
     *
7176
     * @return int
7177
     */
7178
    public function create_document(
7179
        $courseInfo,
7180
        $content = '',
7181
        $title = '',
7182
        $extension = 'html',
7183
        $parentId = 0,
7184
        $creatorId = 0
7185
    ) {
7186
        if (!empty($courseInfo)) {
7187
            $course_id = $courseInfo['real_id'];
7188
        } else {
7189
            $course_id = api_get_course_int_id();
7190
        }
7191
7192
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
7193
        $sessionId = api_get_session_id();
7194
7195
        // Generates folder
7196
        $result = $this->generate_lp_folder($courseInfo);
7197
        $dir = $result['dir'];
7198
7199
        if (empty($parentId) || $parentId == '/') {
7200
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
7201
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
7202
7203
            if ($parentId === '/') {
7204
                $dir = '/';
7205
            }
7206
7207
            // Please, do not modify this dirname formatting.
7208
            if (strstr($dir, '..')) {
7209
                $dir = '/';
7210
            }
7211
7212
            if (!empty($dir[0]) && $dir[0] == '.') {
7213
                $dir = substr($dir, 1);
7214
            }
7215
            if (!empty($dir[0]) && $dir[0] != '/') {
7216
                $dir = '/'.$dir;
7217
            }
7218
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7219
                $dir .= '/';
7220
            }
7221
        } else {
7222
            $parentInfo = DocumentManager::get_document_data_by_id(
7223
                $parentId,
7224
                $courseInfo['code']
7225
            );
7226
            if (!empty($parentInfo)) {
7227
                $dir = $parentInfo['path'].'/';
7228
            }
7229
        }
7230
7231
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7232
        if (!is_dir($filepath)) {
7233
            $dir = '/';
7234
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7235
        }
7236
7237
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
7238
        // is already escaped twice when it gets here.
7239
        $originalTitle = !empty($title) ? $title : $_POST['title'];
7240
        if (!empty($title)) {
7241
            $title = api_replace_dangerous_char(stripslashes($title));
7242
        } else {
7243
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
7244
        }
7245
7246
        $title = disable_dangerous_file($title);
7247
        $filename = $title;
7248
        $content = !empty($content) ? $content : $_POST['content_lp'];
7249
        $tmp_filename = $filename;
7250
7251
        $i = 0;
7252
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
7253
            $tmp_filename = $filename.'_'.++$i;
7254
        }
7255
7256
        $filename = $tmp_filename.'.'.$extension;
7257
        if ($extension == 'html') {
7258
            $content = stripslashes($content);
7259
            $content = str_replace(
7260
                api_get_path(WEB_COURSE_PATH),
7261
                api_get_path(REL_PATH).'courses/',
7262
                $content
7263
            );
7264
7265
            // Change the path of mp3 to absolute.
7266
            // The first regexp deals with :// urls.
7267
            $content = preg_replace(
7268
                "|(flashvars=\"file=)([^:/]+)/|",
7269
                "$1".api_get_path(
7270
                    REL_COURSE_PATH
7271
                ).$courseInfo['path'].'/document/',
7272
                $content
7273
            );
7274
            // The second regexp deals with audio/ urls.
7275
            $content = preg_replace(
7276
                "|(flashvars=\"file=)([^/]+)/|",
7277
                "$1".api_get_path(
7278
                    REL_COURSE_PATH
7279
                ).$courseInfo['path'].'/document/$2/',
7280
                $content
7281
            );
7282
            // For flv player: To prevent edition problem with firefox,
7283
            // we have to use a strange tip (don't blame me please).
7284
            $content = str_replace(
7285
                '</body>',
7286
                '<style type="text/css">body{}</style></body>',
7287
                $content
7288
            );
7289
        }
7290
7291
        if (!file_exists($filepath.$filename)) {
7292
            if ($fp = @fopen($filepath.$filename, 'w')) {
7293
                fputs($fp, $content);
7294
                fclose($fp);
7295
7296
                $file_size = filesize($filepath.$filename);
7297
                $save_file_path = $dir.$filename;
7298
7299
                $document_id = add_document(
7300
                    $courseInfo,
7301
                    $save_file_path,
7302
                    'file',
7303
                    $file_size,
7304
                    $tmp_filename,
7305
                    '',
7306
                    0, //readonly
7307
                    true,
7308
                    null,
7309
                    $sessionId,
7310
                    $creatorId
7311
                );
7312
7313
                if ($document_id) {
7314
                    api_item_property_update(
7315
                        $courseInfo,
7316
                        TOOL_DOCUMENT,
7317
                        $document_id,
7318
                        'DocumentAdded',
7319
                        $creatorId,
7320
                        null,
7321
                        null,
7322
                        null,
7323
                        null,
7324
                        $sessionId
7325
                    );
7326
7327
                    $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7328
                    $new_title = $originalTitle;
7329
7330
                    if ($new_comment || $new_title) {
7331
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7332
                        $ct = '';
7333
                        if ($new_comment) {
7334
                            $ct .= ", comment='".Database::escape_string($new_comment)."'";
7335
                        }
7336
                        if ($new_title) {
7337
                            $ct .= ", title='".Database::escape_string($new_title)."' ";
7338
                        }
7339
7340
                        $sql = "UPDATE ".$tbl_doc." SET ".substr($ct, 1)."
7341
                               WHERE c_id = ".$course_id." AND id = ".$document_id;
7342
                        Database::query($sql);
7343
                    }
7344
                }
7345
7346
                return $document_id;
7347
            }
7348
        }
7349
    }
7350
7351
    /**
7352
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
7353
     *
7354
     * @param array $_course array
7355
     */
7356
    public function edit_document($_course)
7357
    {
7358
        $course_id = api_get_course_int_id();
7359
        $urlAppend = api_get_configuration_value('url_append');
7360
        // Please, do not modify this dirname formatting.
7361
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
7362
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
7363
7364
        if (strstr($dir, '..')) {
7365
            $dir = '/';
7366
        }
7367
7368
        if (isset($dir[0]) && $dir[0] == '.') {
7369
            $dir = substr($dir, 1);
7370
        }
7371
7372
        if (isset($dir[0]) && $dir[0] != '/') {
7373
            $dir = '/'.$dir;
7374
        }
7375
7376
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7377
            $dir .= '/';
7378
        }
7379
7380
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
7381
        if (!is_dir($filepath)) {
7382
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
7383
        }
7384
7385
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
7386
7387
        if (isset($_POST['path']) && !empty($_POST['path'])) {
7388
            $document_id = (int) $_POST['path'];
7389
            $documentInfo = DocumentManager::get_document_data_by_id($document_id, api_get_course_id(), false, null, true);
7390
            if (empty($documentInfo)) {
7391
                // Try with iid
7392
                $table = Database::get_course_table(TABLE_DOCUMENT);
7393
                $sql = "SELECT id, path FROM $table
7394
                        WHERE c_id = $course_id AND iid = $document_id AND path NOT LIKE '%_DELETED_%' ";
7395
                $res_doc = Database::query($sql);
7396
                $row = Database::fetch_array($res_doc);
7397
                if ($row) {
7398
                    $document_id = $row['id'];
7399
                    $documentPath = $row['path'];
7400
                }
7401
            } else {
7402
                $documentPath = $documentInfo['path'];
7403
            }
7404
7405
            $content = stripslashes($_POST['content_lp']);
7406
            $file = $filepath.$documentPath;
7407
7408
            if (!file_exists($file)) {
7409
                return false;
7410
            }
7411
7412
            if ($fp = @fopen($file, 'w')) {
7413
                $content = str_replace(
7414
                    api_get_path(WEB_COURSE_PATH),
7415
                    $urlAppend.api_get_path(REL_COURSE_PATH),
7416
                    $content
7417
                );
7418
                // Change the path of mp3 to absolute.
7419
                // The first regexp deals with :// urls.
7420
                $content = preg_replace(
7421
                    "|(flashvars=\"file=)([^:/]+)/|",
7422
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
7423
                    $content
7424
                );
7425
                // The second regexp deals with audio/ urls.
7426
                $content = preg_replace(
7427
                    "|(flashvars=\"file=)([^:/]+)/|",
7428
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
7429
                    $content
7430
                );
7431
                fputs($fp, $content);
7432
                fclose($fp);
7433
7434
                $sql = "UPDATE $table_doc SET
7435
                            title='".Database::escape_string($_POST['title'])."'
7436
                        WHERE c_id = $course_id AND id = ".$document_id;
7437
                Database::query($sql);
7438
            }
7439
        }
7440
    }
7441
7442
    /**
7443
     * Displays the selected item, with a panel for manipulating the item.
7444
     *
7445
     * @param int    $item_id
7446
     * @param string $msg
7447
     * @param bool   $show_actions
7448
     *
7449
     * @return string
7450
     */
7451
    public function display_item($item_id, $msg = null, $show_actions = true)
7452
    {
7453
        $course_id = api_get_course_int_id();
7454
        $return = '';
7455
        if (is_numeric($item_id)) {
7456
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7457
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
7458
                    WHERE lp.iid = ".intval($item_id);
7459
            $result = Database::query($sql);
7460
            while ($row = Database::fetch_array($result, 'ASSOC')) {
7461
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
7462
7463
                // Prevents wrong parent selection for document, see Bug#1251.
7464
                if ($row['item_type'] != 'dir') {
7465
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
7466
                }
7467
7468
                if ($show_actions) {
7469
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7470
                }
7471
                $return .= '<div style="padding:10px;">';
7472
7473
                if ($msg != '') {
7474
                    $return .= $msg;
7475
                }
7476
7477
                $return .= '<h3>'.$row['title'].'</h3>';
7478
7479
                switch ($row['item_type']) {
7480
                    case TOOL_THREAD:
7481
                        $link = $this->rl_get_resource_link_for_learnpath(
7482
                            $course_id,
7483
                            $row['lp_id'],
7484
                            $item_id,
7485
                            0
7486
                        );
7487
                        $return .= Display::url(
7488
                            get_lang('GoToThread'),
7489
                            $link,
7490
                            ['class' => 'btn btn-primary']
7491
                        );
7492
                        break;
7493
                    case TOOL_FORUM:
7494
                        $return .= Display::url(
7495
                            get_lang('GoToForum'),
7496
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
7497
                            ['class' => 'btn btn-primary']
7498
                        );
7499
                        break;
7500
                    case TOOL_QUIZ:
7501
                        if (!empty($row['path'])) {
7502
                            $exercise = new Exercise();
7503
                            $exercise->read($row['path']);
7504
                            $return .= $exercise->description.'<br />';
7505
                            $return .= Display::url(
7506
                                get_lang('GoToExercise'),
7507
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
7508
                                ['class' => 'btn btn-primary']
7509
                            );
7510
                        }
7511
                        break;
7512
                    case TOOL_LP_FINAL_ITEM:
7513
                        $return .= $this->getSavedFinalItem();
7514
                        break;
7515
                    case TOOL_DOCUMENT:
7516
                    case TOOL_READOUT_TEXT:
7517
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7518
                        $sql_doc = "SELECT path FROM $tbl_doc
7519
                                    WHERE c_id = $course_id AND iid = ".intval($row['path']);
7520
                        $result = Database::query($sql_doc);
7521
                        $path_file = Database::result($result, 0, 0);
7522
                        $path_parts = pathinfo($path_file);
7523
                        // TODO: Correct the following naive comparisons.
7524
                        if (in_array($path_parts['extension'], [
7525
                            'html',
7526
                            'txt',
7527
                            'png',
7528
                            'jpg',
7529
                            'JPG',
7530
                            'jpeg',
7531
                            'JPEG',
7532
                            'gif',
7533
                            'swf',
7534
                            'pdf',
7535
                            'htm',
7536
                        ])) {
7537
                            $return .= $this->display_document($row['path'], true, true);
7538
                        }
7539
                        break;
7540
                    case TOOL_HOTPOTATOES:
7541
                        $return .= $this->display_document($row['path'], false, true);
7542
                        break;
7543
                }
7544
                $return .= '</div>';
7545
            }
7546
        }
7547
7548
        return $return;
7549
    }
7550
7551
    /**
7552
     * Shows the needed forms for editing a specific item.
7553
     *
7554
     * @param int $item_id
7555
     *
7556
     * @throws Exception
7557
     * @throws HTML_QuickForm_Error
7558
     *
7559
     * @return string
7560
     */
7561
    public function display_edit_item($item_id, $excludeExtraFields = [])
7562
    {
7563
        $course_id = api_get_course_int_id();
7564
        $return = '';
7565
        $item_id = (int) $item_id;
7566
7567
        if (empty($item_id)) {
7568
            return '';
7569
        }
7570
7571
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7572
        $sql = "SELECT * FROM $tbl_lp_item
7573
                WHERE iid = ".$item_id;
7574
        $res = Database::query($sql);
7575
        $row = Database::fetch_array($res);
7576
        switch ($row['item_type']) {
7577
            case 'dir':
7578
            case 'asset':
7579
            case 'sco':
7580
            if (isset($_GET['view']) && $_GET['view'] == 'build') {
7581
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7582
                $return .= $this->display_item_form(
7583
                        $row['item_type'],
7584
                        get_lang('EditCurrentChapter').' :',
7585
                        'edit',
7586
                        $item_id,
7587
                        $row
7588
                    );
7589
            } else {
7590
                $return .= $this->display_item_form(
7591
                        $row['item_type'],
7592
                        get_lang('EditCurrentChapter').' :',
7593
                        'edit_item',
7594
                        $item_id,
7595
                        $row
7596
                    );
7597
            }
7598
                break;
7599
            case TOOL_DOCUMENT:
7600
            case TOOL_READOUT_TEXT:
7601
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7602
                $sql = "SELECT lp.*, doc.path as dir
7603
                        FROM $tbl_lp_item as lp
7604
                        LEFT JOIN $tbl_doc as doc
7605
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7606
                        WHERE
7607
                            doc.c_id = $course_id AND
7608
                            lp.iid = ".$item_id;
7609
                $res_step = Database::query($sql);
7610
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7611
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7612
7613
                if ($row['item_type'] === TOOL_DOCUMENT) {
7614
                    $return .= $this->display_document_form(
7615
                        'edit',
7616
                        $item_id,
7617
                        $row_step,
7618
                        null,
7619
                        $excludeExtraFields
7620
                    );
7621
                }
7622
7623
                if ($row['item_type'] === TOOL_READOUT_TEXT) {
7624
                    $return .= $this->displayFrmReadOutText('edit', $item_id, $row_step);
7625
                }
7626
                break;
7627
            case TOOL_LINK:
7628
                $linkId = (int) $row['path'];
7629
                if (!empty($linkId)) {
7630
                    $table = Database::get_course_table(TABLE_LINK);
7631
                    $sql = 'SELECT url FROM '.$table.'
7632
                            WHERE c_id = '.$course_id.' AND iid = '.$linkId;
7633
                    $res_link = Database::query($sql);
7634
                    $row_link = Database::fetch_array($res_link);
7635
                    if (empty($row_link)) {
7636
                        // Try with id
7637
                        $sql = 'SELECT url FROM '.$table.'
7638
                                WHERE c_id = '.$course_id.' AND id = '.$linkId;
7639
                        $res_link = Database::query($sql);
7640
                        $row_link = Database::fetch_array($res_link);
7641
                    }
7642
7643
                    if (is_array($row_link)) {
7644
                        $row['url'] = $row_link['url'];
7645
                    }
7646
                }
7647
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7648
                $return .= $this->display_link_form('edit', $item_id, $row, null, $excludeExtraFields);
7649
                break;
7650
            case TOOL_LP_FINAL_ITEM:
7651
                Session::write('finalItem', true);
7652
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7653
                $sql = "SELECT lp.*, doc.path as dir
7654
                        FROM $tbl_lp_item as lp
7655
                        LEFT JOIN $tbl_doc as doc
7656
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7657
                        WHERE
7658
                            doc.c_id = $course_id AND
7659
                            lp.iid = ".$item_id;
7660
                $res_step = Database::query($sql);
7661
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7662
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7663
                $return .= $this->display_document_form(
7664
                    'edit',
7665
                    $item_id,
7666
                    $row_step,
7667
                    null,
7668
                    $excludeExtraFields
7669
                );
7670
                break;
7671
            case TOOL_QUIZ:
7672
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7673
                $return .= $this->display_quiz_form('edit', $item_id, $row, $excludeExtraFields);
7674
                break;
7675
            case TOOL_HOTPOTATOES:
7676
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7677
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7678
                break;
7679
            case TOOL_STUDENTPUBLICATION:
7680
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7681
                $return .= $this->display_student_publication_form('edit', $item_id, $row, null, $excludeExtraFields);
7682
                break;
7683
            case TOOL_FORUM:
7684
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7685
                $return .= $this->display_forum_form('edit', $item_id, $row, $excludeExtraFields);
7686
                break;
7687
            case TOOL_THREAD:
7688
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7689
                $return .= $this->display_thread_form('edit', $item_id, $row);
7690
                break;
7691
            case TOOL_SURVEY:
7692
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7693
                $return .= $this->displaySurveyForm('edit', $item_id, $row);
7694
                break;
7695
        }
7696
7697
        return $return;
7698
    }
7699
7700
    /**
7701
     * Function that displays a list with al the resources that
7702
     * could be added to the learning path.
7703
     *
7704
     * @throws Exception
7705
     * @throws HTML_QuickForm_Error
7706
     *
7707
     * @return bool
7708
     */
7709
    public function display_resources()
7710
    {
7711
        $course_code = api_get_course_id();
7712
7713
        // Get all the docs.
7714
        $documents = $this->get_documents(true);
7715
7716
        // Get all the exercises.
7717
        $exercises = $this->get_exercises();
7718
7719
        // Get all the links.
7720
        $links = $this->get_links();
7721
7722
        // Get all the student publications.
7723
        $works = $this->get_student_publications();
7724
7725
        // Get all the forums.
7726
        $forums = $this->get_forums(null, $course_code);
7727
7728
        $dir = $this->display_item_form('dir', get_lang('EnterDataNewChapter'), 'add_item');
7729
7730
        // Get the final item form (see BT#11048) .
7731
        $finish = $this->getFinalItemForm();
7732
7733
        $headers = [
7734
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7735
            Display::return_icon('quiz.png', get_lang('Quiz'), [], ICON_SIZE_BIG),
7736
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7737
            Display::return_icon('works.png', get_lang('Works'), [], ICON_SIZE_BIG),
7738
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7739
            Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), [], ICON_SIZE_BIG),
7740
        ];
7741
7742
        $items = [
7743
            $documents,
7744
            $exercises,
7745
            $links,
7746
            $works,
7747
            $forums,
7748
            $dir,
7749
        ];
7750
7751
        $allowSurveyTool = api_get_configuration_value('allow_survey_tool_in_lp');
7752
        if ($allowSurveyTool) {
7753
            // Get all the surveys
7754
            $surveys = $this->getSurveys();
7755
            $items[] = $surveys;
7756
            $headers[] = Display::return_icon('survey.png', get_lang('Surveys'), [], ICON_SIZE_BIG);
7757
        }
7758
7759
        $xApiPlugin = XApiPlugin::create();
7760
        if ($xApiPlugin->isEnabled()) {
7761
            $headers[] = Display::return_icon(
7762
                'import_scorm.png',
7763
                get_lang($xApiPlugin->get_lang('ToolTinCan')),
7764
                [],
7765
                ICON_SIZE_BIG
7766
            );
7767
            $items[] = $xApiPlugin->getLpResourceBlock($this->lp_id);
7768
        }
7769
7770
        $h5pImportPlugin = H5pImportPlugin::create();
7771
        if ($h5pImportPlugin->isEnabled()) {
7772
            $headers[] = Display::return_icon(
7773
                'plugin_h5p_import_upload.png',
7774
                $h5pImportPlugin->get_lang('plugin_title'),
7775
                [],
7776
                ICON_SIZE_BIG
7777
            );
7778
            $items[] = $h5pImportPlugin->getLpResourceBlock($this->lp_id);
7779
        }
7780
7781
        $headers[] = Display::return_icon('flag_checkered.png', get_lang('Certificate'), [], ICON_SIZE_BIG);
7782
        $items[] = $finish;
7783
7784
        echo Display::return_message(get_lang('ClickOnTheLearnerViewToSeeYourLearningPath'), 'normal');
7785
7786
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
7787
7788
        echo Display::tabs(
7789
            $headers,
7790
            $items,
7791
            'resource_tab',
7792
            [],
7793
            [],
7794
            $selected
7795
        );
7796
7797
        return true;
7798
    }
7799
7800
    /**
7801
     * Returns the extension of a document.
7802
     *
7803
     * @param string $filename
7804
     *
7805
     * @return string Extension (part after the last dot)
7806
     */
7807
    public function get_extension($filename)
7808
    {
7809
        $explode = explode('.', $filename);
7810
7811
        return $explode[count($explode) - 1];
7812
    }
7813
7814
    /**
7815
     * Displays a document by id.
7816
     *
7817
     * @param int  $id
7818
     * @param bool $show_title
7819
     * @param bool $iframe
7820
     * @param bool $edit_link
7821
     *
7822
     * @return string
7823
     */
7824
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7825
    {
7826
        $_course = api_get_course_info();
7827
        $course_id = api_get_course_int_id();
7828
        $id = (int) $id;
7829
        $return = '';
7830
        $table = Database::get_course_table(TABLE_DOCUMENT);
7831
        $sql_doc = "SELECT * FROM $table
7832
                    WHERE c_id = $course_id AND iid = $id";
7833
        $res_doc = Database::query($sql_doc);
7834
        $row_doc = Database::fetch_array($res_doc);
7835
7836
        // TODO: Add a path filter.
7837
        if ($iframe) {
7838
            $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>';
7839
        } else {
7840
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7841
        }
7842
7843
        return $return;
7844
    }
7845
7846
    /**
7847
     * Return HTML form to add/edit a quiz.
7848
     *
7849
     * @param string $action     Action (add/edit)
7850
     * @param int    $id         Item ID if already exists
7851
     * @param mixed  $extra_info Extra information (quiz ID if integer)
7852
     *
7853
     * @throws Exception
7854
     *
7855
     * @return string HTML form
7856
     */
7857
    public function display_quiz_form(
7858
        $action = 'add',
7859
        $id = 0,
7860
        $extra_info = '',
7861
        $excludeExtraFields = []
7862
    ) {
7863
        $course_id = api_get_course_int_id();
7864
        $id = (int) $id;
7865
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7866
7867
        if ($id != 0 && is_array($extra_info)) {
7868
            $item_title = $extra_info['title'];
7869
            $item_description = $extra_info['description'];
7870
        } elseif (is_numeric($extra_info)) {
7871
            $sql = "SELECT title, description
7872
                    FROM $tbl_quiz
7873
                    WHERE iid = $extra_info";
7874
7875
            $result = Database::query($sql);
7876
            $row = Database::fetch_array($result);
7877
            $item_title = $row['title'];
7878
            $item_description = $row['description'];
7879
        } else {
7880
            $item_title = '';
7881
            $item_description = '';
7882
        }
7883
        $item_title = Security::remove_XSS($item_title);
7884
        $item_description = Security::remove_XSS($item_description);
7885
7886
        $parent = 0;
7887
        if ($id != 0 && is_array($extra_info)) {
7888
            $parent = $extra_info['parent_item_id'];
7889
        }
7890
7891
        $arrLP = $this->getItemsForForm();
7892
        $this->tree_array($arrLP);
7893
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7894
        unset($this->arrMenu);
7895
7896
        $form = new FormValidator(
7897
            'quiz_form',
7898
            'POST',
7899
            $this->getCurrentBuildingModeURL()
7900
        );
7901
        $defaults = [];
7902
7903
        if ($action === 'add') {
7904
            $legend = get_lang('CreateTheExercise');
7905
        } elseif ($action === 'move') {
7906
            $legend = get_lang('MoveTheCurrentExercise');
7907
        } else {
7908
            $legend = get_lang('EditCurrentExecice');
7909
        }
7910
7911
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7912
            $legend .= Display::return_message(get_lang('Warning').' ! '.get_lang('WarningEditingDocument'));
7913
        }
7914
7915
        $form->addHeader($legend);
7916
7917
        if ($action != 'move') {
7918
            $this->setItemTitle($form);
7919
            $defaults['title'] = $item_title;
7920
        }
7921
7922
        // Select for Parent item, root or chapter
7923
        $selectParent = $form->addSelect(
7924
            'parent',
7925
            get_lang('Parent'),
7926
            [],
7927
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7928
        );
7929
        $selectParent->addOption($this->name, 0);
7930
7931
        $arrHide = [
7932
            $id,
7933
        ];
7934
        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...
7935
            if ($action != 'add') {
7936
                if (
7937
                    ($arrLP[$i]['item_type'] == 'dir') &&
7938
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7939
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7940
                ) {
7941
                    $selectParent->addOption(
7942
                        $arrLP[$i]['title'],
7943
                        $arrLP[$i]['id'],
7944
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7945
                    );
7946
7947
                    if ($parent == $arrLP[$i]['id']) {
7948
                        $selectParent->setSelected($arrLP[$i]['id']);
7949
                    }
7950
                } else {
7951
                    $arrHide[] = $arrLP[$i]['id'];
7952
                }
7953
            } else {
7954
                if ($arrLP[$i]['item_type'] == 'dir') {
7955
                    $selectParent->addOption(
7956
                        $arrLP[$i]['title'],
7957
                        $arrLP[$i]['id'],
7958
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7959
                    );
7960
7961
                    if ($parent == $arrLP[$i]['id']) {
7962
                        $selectParent->setSelected($arrLP[$i]['id']);
7963
                    }
7964
                }
7965
            }
7966
        }
7967
7968
        if (is_array($arrLP)) {
7969
            reset($arrLP);
7970
        }
7971
7972
        $selectPrevious = $form->addSelect(
7973
            'previous',
7974
            get_lang('Position'),
7975
            [],
7976
            ['id' => 'previous']
7977
        );
7978
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7979
7980
        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...
7981
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7982
                $arrLP[$i]['id'] != $id
7983
            ) {
7984
                $selectPrevious->addOption(
7985
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7986
                    $arrLP[$i]['id']
7987
                );
7988
7989
                if (is_array($extra_info)) {
7990
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7991
                        $selectPrevious->setSelected($arrLP[$i]['id']);
7992
                    }
7993
                } elseif ($action == 'add') {
7994
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7995
                }
7996
            }
7997
        }
7998
7999
        if ($action != 'move') {
8000
            $arrHide = [];
8001
            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...
8002
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8003
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8004
                }
8005
            }
8006
        }
8007
8008
        if ('edit' === $action) {
8009
            if (true !== api_get_configuration_value('lp_item_prerequisite_dates')) {
8010
                $excludeExtraFields = array_merge($excludeExtraFields, ['start_date', 'end_date']);
8011
            }
8012
            $extraField = new ExtraField('lp_item');
8013
            $extraField->addElements($form, $id, $excludeExtraFields);
8014
        }
8015
8016
        if ($action === 'add') {
8017
            $form->addButtonSave(get_lang('AddExercise'), 'submit_button');
8018
        } else {
8019
            $form->addButtonSave(get_lang('EditCurrentExecice'), 'submit_button');
8020
        }
8021
8022
        if ($action === 'move') {
8023
            $form->addHidden('title', $item_title);
8024
            $form->addHidden('description', $item_description);
8025
        }
8026
8027
        if (is_numeric($extra_info)) {
8028
            $form->addHidden('path', $extra_info);
8029
        } elseif (is_array($extra_info)) {
8030
            $form->addHidden('path', $extra_info['path']);
8031
        }
8032
8033
        $form->addHidden('type', TOOL_QUIZ);
8034
        $form->addHidden('post_time', time());
8035
        $this->setAuthorLpItem($form);
8036
        $form->setDefaults($defaults);
8037
8038
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
8039
    }
8040
8041
    /**
8042
     * Addition of Hotpotatoes tests.
8043
     *
8044
     * @param string $action
8045
     * @param int    $id         Internal ID of the item
8046
     * @param string $extra_info
8047
     *
8048
     * @return string HTML structure to display the hotpotatoes addition formular
8049
     */
8050
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
8051
    {
8052
        $course_id = api_get_course_int_id();
8053
        $uploadPath = DIR_HOTPOTATOES;
8054
8055
        if ($id != 0 && is_array($extra_info)) {
8056
            $item_title = stripslashes($extra_info['title']);
8057
            $item_description = stripslashes($extra_info['description']);
8058
        } elseif (is_numeric($extra_info)) {
8059
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
8060
8061
            $sql = "SELECT * FROM $TBL_DOCUMENT
8062
                    WHERE
8063
                        c_id = $course_id AND
8064
                        path LIKE '".$uploadPath."/%/%htm%' AND
8065
                        iid = ".(int) $extra_info."
8066
                    ORDER BY iid ASC";
8067
8068
            $res_hot = Database::query($sql);
8069
            $row = Database::fetch_array($res_hot);
8070
8071
            $item_title = $row['title'];
8072
            $item_description = $row['description'];
8073
8074
            if (!empty($row['comment'])) {
8075
                $item_title = $row['comment'];
8076
            }
8077
        } else {
8078
            $item_title = '';
8079
            $item_description = '';
8080
        }
8081
8082
        $parent = 0;
8083
        if ($id != 0 && is_array($extra_info)) {
8084
            $parent = $extra_info['parent_item_id'];
8085
        }
8086
8087
        $arrLP = $this->getItemsForForm();
8088
        $legend = '<legend>';
8089
        if ($action == 'add') {
8090
            $legend .= get_lang('CreateTheExercise');
8091
        } elseif ($action == 'move') {
8092
            $legend .= get_lang('MoveTheCurrentExercise');
8093
        } else {
8094
            $legend .= get_lang('EditCurrentExecice');
8095
        }
8096
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
8097
            $legend .= Display::return_message(
8098
                get_lang('Warning').' ! '.get_lang('WarningEditingDocument')
8099
            );
8100
        }
8101
        $legend .= '</legend>';
8102
8103
        $return = '<form method="POST">';
8104
        $return .= $legend;
8105
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
8106
        $return .= '<tr>';
8107
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
8108
        $return .= '<td class="input">';
8109
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
8110
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
8111
        $arrHide = [$id];
8112
8113
        if (count($arrLP) > 0) {
8114
            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...
8115
                if ($action != 'add') {
8116
                    if ($arrLP[$i]['item_type'] == 'dir' &&
8117
                        !in_array($arrLP[$i]['id'], $arrHide) &&
8118
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8119
                    ) {
8120
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
8121
                    } else {
8122
                        $arrHide[] = $arrLP[$i]['id'];
8123
                    }
8124
                } else {
8125
                    if ($arrLP[$i]['item_type'] == 'dir') {
8126
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
8127
                    }
8128
                }
8129
            }
8130
            reset($arrLP);
8131
        }
8132
8133
        $return .= '</select>';
8134
        $return .= '</td>';
8135
        $return .= '</tr>';
8136
        $return .= '<tr>';
8137
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
8138
        $return .= '<td class="input">';
8139
        $return .= '<select id="previous" name="previous" size="1">';
8140
        $return .= '<option class="top" value="0">'.get_lang('FirstPosition').'</option>';
8141
8142
        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...
8143
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8144
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8145
                    $selected = 'selected="selected" ';
8146
                } elseif ($action == 'add') {
8147
                    $selected = 'selected="selected" ';
8148
                } else {
8149
                    $selected = '';
8150
                }
8151
8152
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.
8153
                    get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
8154
            }
8155
        }
8156
8157
        $return .= '</select>';
8158
        $return .= '</td>';
8159
        $return .= '</tr>';
8160
8161
        if ($action != 'move') {
8162
            $return .= '<tr>';
8163
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
8164
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
8165
            $return .= '</tr>';
8166
            $id_prerequisite = 0;
8167
            if (is_array($arrLP) && count($arrLP) > 0) {
8168
                foreach ($arrLP as $key => $value) {
8169
                    if ($value['id'] == $id) {
8170
                        $id_prerequisite = $value['prerequisite'];
8171
                        break;
8172
                    }
8173
                }
8174
8175
                $arrHide = [];
8176
                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...
8177
                    if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8178
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8179
                    }
8180
                }
8181
            }
8182
        }
8183
8184
        $return .= '<tr>';
8185
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.
8186
            get_lang('SaveHotpotatoes').'</button></td>';
8187
        $return .= '</tr>';
8188
        $return .= '</table>';
8189
8190
        if ($action == 'move') {
8191
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
8192
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
8193
        }
8194
8195
        if (is_numeric($extra_info)) {
8196
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
8197
        } elseif (is_array($extra_info)) {
8198
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
8199
        }
8200
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
8201
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
8202
        $return .= '</form>';
8203
8204
        return $return;
8205
    }
8206
8207
    public function displaySurveyForm(
8208
      $action = 'add',
8209
      $id = 0,
8210
      $extraInfo = '',
8211
      $excludeExtraFields = []
8212
    ) {
8213
        $courseId = api_get_course_int_id();
8214
        $tblSurvey = Database::get_course_table(TABLE_SURVEY);
8215
8216
        $itemTitle = '';
8217
        $itemDescription = '';
8218
8219
        if ($id != 0 && is_array($extraInfo)) {
8220
            $itemTitle = stripslashes($extraInfo['title']);
8221
        } elseif (is_numeric($extraInfo)) {
8222
            $sql = "SELECT title, intro as comment
8223
                    FROM $tblSurvey
8224
                    WHERE c_id = $courseId AND survey_id = ".(int) $extraInfo;
8225
8226
            $result = Database::query($sql);
8227
            $row = Database::fetch_array($result);
8228
8229
            $itemTitle = strip_tags($row['title']);
8230
            $itemDescription = $row['comment'];
8231
        }
8232
        $parent = 0;
8233
        if ($id != 0 && is_array($extraInfo)) {
8234
            $parent = $extraInfo['parent_item_id'];
8235
        }
8236
        $arrLP = $this->getItemsForForm();
8237
        $this->tree_array($arrLP);
8238
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8239
        unset($this->arrMenu);
8240
8241
        if ($action == 'add') {
8242
            $legend = get_lang('CreateSurvey');
8243
        } elseif ($action == 'move') {
8244
            $legend = get_lang('MoveTheCurrentSurvey');
8245
        } else {
8246
            $legend = get_lang('ModifySurveyInformation');
8247
        }
8248
8249
        $form = new FormValidator(
8250
            'survey_form',
8251
            'POST',
8252
            $this->getCurrentBuildingModeURL()
8253
        );
8254
        $defaults = [];
8255
8256
        $form->addHeader($legend);
8257
8258
        if ($action != 'move') {
8259
            $this->setItemTitle($form);
8260
            $defaults['title'] = $itemTitle;
8261
        }
8262
8263
        $selectParent = $form->addSelect(
8264
            'parent',
8265
            get_lang('Parent'),
8266
            [],
8267
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
8268
        );
8269
        $selectParent->addOption($this->name, 0);
8270
        $arrHide = [
8271
            $id,
8272
        ];
8273
        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...
8274
            if ($action != 'add') {
8275
                if ($arrLP[$i]['item_type'] == 'dir' &&
8276
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8277
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8278
                ) {
8279
                    $selectParent->addOption(
8280
                        $arrLP[$i]['title'],
8281
                        $arrLP[$i]['id'],
8282
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8283
                    );
8284
8285
                    if ($parent == $arrLP[$i]['id']) {
8286
                        $selectParent->setSelected($arrLP[$i]['id']);
8287
                    }
8288
                } else {
8289
                    $arrHide[] = $arrLP[$i]['id'];
8290
                }
8291
            } else {
8292
                if ($arrLP[$i]['item_type'] == 'dir') {
8293
                    $selectParent->addOption(
8294
                        $arrLP[$i]['title'],
8295
                        $arrLP[$i]['id'],
8296
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8297
                    );
8298
8299
                    if ($parent == $arrLP[$i]['id']) {
8300
                        $selectParent->setSelected($arrLP[$i]['id']);
8301
                    }
8302
                }
8303
            }
8304
        }
8305
8306
        if (is_array($arrLP)) {
8307
            reset($arrLP);
8308
        }
8309
8310
        $selectPrevious = $form->addSelect(
8311
            'previous',
8312
            get_lang('Position'),
8313
            [],
8314
            ['id' => 'previous', 'class' => 'learnpath_item_form']
8315
        );
8316
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8317
8318
        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...
8319
            if ($arrLP[$i]['parent_item_id'] == $parent &&
8320
                $arrLP[$i]['id'] != $id
8321
            ) {
8322
                $selectPrevious->addOption(
8323
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8324
                    $arrLP[$i]['id']
8325
                );
8326
8327
                if (isset($extra_info['previous_item_id']) &&
8328
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8329
                ) {
8330
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8331
                } elseif ($action == 'add') {
8332
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8333
                }
8334
            }
8335
        }
8336
8337
        if ($action != 'move') {
8338
            $idPrerequisite = 0;
8339
            if (is_array($arrLP)) {
8340
                foreach ($arrLP as $key => $value) {
8341
                    if ($value['id'] == $id) {
8342
                        $idPrerequisite = $value['prerequisite'];
8343
                        break;
8344
                    }
8345
                }
8346
            }
8347
8348
            $arrHide = [];
8349
            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...
8350
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8351
                    if (isset($extra_info['previous_item_id']) &&
8352
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
8353
                    ) {
8354
                        $sSelectedPosition = $arrLP[$i]['id'];
8355
                    } elseif ($action == 'add') {
8356
                        $sSelectedPosition = 0;
8357
                    }
8358
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8359
                }
8360
            }
8361
        }
8362
8363
        if ('edit' === $action) {
8364
            if (true !== api_get_configuration_value('lp_item_prerequisite_dates')) {
8365
                $excludeExtraFields = array_merge($excludeExtraFields, ['start_date', 'end_date']);
8366
            }
8367
            $extraField = new ExtraField('lp_item');
8368
            $extraField->addElements($form, $id, $excludeExtraFields);
8369
        }
8370
8371
        if ($action == 'add') {
8372
            $form->addButtonSave(get_lang('AddSurveyToCourse'), 'submit_button');
8373
        } else {
8374
            $form->addButtonSave(get_lang('EditCurrentSurvey'), 'submit_button');
8375
        }
8376
8377
        if ($action == 'move') {
8378
            $form->addHidden('title', $itemTitle);
8379
            $form->addHidden('description', $itemDescription);
8380
        }
8381
8382
        if (is_numeric($extraInfo)) {
8383
            $form->addHidden('path', $extraInfo);
8384
        } elseif (is_array($extraInfo)) {
8385
            $form->addHidden('path', $extraInfo['path']);
8386
        }
8387
        $form->addHidden('type', TOOL_SURVEY);
8388
        $form->addHidden('post_time', time());
8389
        $this->setAuthorLpItem($form);
8390
        $form->setDefaults($defaults);
8391
8392
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
8393
    }
8394
8395
    /**
8396
     * Return the form to display the forum edit/add option.
8397
     *
8398
     * @param string $action
8399
     * @param int    $id         ID of the lp_item if already exists
8400
     * @param string $extra_info
8401
     *
8402
     * @throws Exception
8403
     *
8404
     * @return string HTML form
8405
     */
8406
    public function display_forum_form(
8407
        $action = 'add',
8408
        $id = 0,
8409
        $extra_info = '',
8410
        $excludeExtraFields = []
8411
    ) {
8412
        $course_id = api_get_course_int_id();
8413
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
8414
8415
        $item_title = '';
8416
        $item_description = '';
8417
8418
        if ($id != 0 && is_array($extra_info)) {
8419
            $item_title = stripslashes($extra_info['title']);
8420
        } elseif (is_numeric($extra_info)) {
8421
            $sql = "SELECT forum_title as title, forum_comment as comment
8422
                    FROM $tbl_forum
8423
                    WHERE c_id = $course_id AND forum_id = ".$extra_info;
8424
8425
            $result = Database::query($sql);
8426
            $row = Database::fetch_array($result);
8427
8428
            $item_title = $row['title'];
8429
            $item_description = $row['comment'];
8430
        }
8431
        $parent = 0;
8432
        if ($id != 0 && is_array($extra_info)) {
8433
            $parent = $extra_info['parent_item_id'];
8434
        }
8435
        $arrLP = $this->getItemsForForm();
8436
        $this->tree_array($arrLP);
8437
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8438
        unset($this->arrMenu);
8439
8440
        if ($action == 'add') {
8441
            $legend = get_lang('CreateTheForum');
8442
        } elseif ($action == 'move') {
8443
            $legend = get_lang('MoveTheCurrentForum');
8444
        } else {
8445
            $legend = get_lang('EditCurrentForum');
8446
        }
8447
8448
        $form = new FormValidator(
8449
            'forum_form',
8450
            'POST',
8451
            $this->getCurrentBuildingModeURL()
8452
        );
8453
        $defaults = [];
8454
8455
        $form->addHeader($legend);
8456
8457
        if ($action != 'move') {
8458
            $this->setItemTitle($form);
8459
            $defaults['title'] = $item_title;
8460
        }
8461
8462
        $selectParent = $form->addSelect(
8463
            'parent',
8464
            get_lang('Parent'),
8465
            [],
8466
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
8467
        );
8468
        $selectParent->addOption($this->name, 0);
8469
        $arrHide = [
8470
            $id,
8471
        ];
8472
        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...
8473
            if ($action != 'add') {
8474
                if ($arrLP[$i]['item_type'] == 'dir' &&
8475
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8476
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8477
                ) {
8478
                    $selectParent->addOption(
8479
                        $arrLP[$i]['title'],
8480
                        $arrLP[$i]['id'],
8481
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8482
                    );
8483
8484
                    if ($parent == $arrLP[$i]['id']) {
8485
                        $selectParent->setSelected($arrLP[$i]['id']);
8486
                    }
8487
                } else {
8488
                    $arrHide[] = $arrLP[$i]['id'];
8489
                }
8490
            } else {
8491
                if ($arrLP[$i]['item_type'] == 'dir') {
8492
                    $selectParent->addOption(
8493
                        $arrLP[$i]['title'],
8494
                        $arrLP[$i]['id'],
8495
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8496
                    );
8497
8498
                    if ($parent == $arrLP[$i]['id']) {
8499
                        $selectParent->setSelected($arrLP[$i]['id']);
8500
                    }
8501
                }
8502
            }
8503
        }
8504
8505
        if (is_array($arrLP)) {
8506
            reset($arrLP);
8507
        }
8508
8509
        $selectPrevious = $form->addSelect(
8510
            'previous',
8511
            get_lang('Position'),
8512
            [],
8513
            ['id' => 'previous', 'class' => 'learnpath_item_form']
8514
        );
8515
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8516
8517
        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...
8518
            if ($arrLP[$i]['parent_item_id'] == $parent &&
8519
                $arrLP[$i]['id'] != $id
8520
            ) {
8521
                $selectPrevious->addOption(
8522
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8523
                    $arrLP[$i]['id']
8524
                );
8525
8526
                if (isset($extra_info['previous_item_id']) &&
8527
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8528
                ) {
8529
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8530
                } elseif ($action == 'add') {
8531
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8532
                }
8533
            }
8534
        }
8535
8536
        if ($action != 'move') {
8537
            $id_prerequisite = 0;
8538
            if (is_array($arrLP)) {
8539
                foreach ($arrLP as $key => $value) {
8540
                    if ($value['id'] == $id) {
8541
                        $id_prerequisite = $value['prerequisite'];
8542
                        break;
8543
                    }
8544
                }
8545
            }
8546
8547
            $arrHide = [];
8548
            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...
8549
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8550
                    if (isset($extra_info['previous_item_id']) &&
8551
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
8552
                    ) {
8553
                        $s_selected_position = $arrLP[$i]['id'];
8554
                    } elseif ($action == 'add') {
8555
                        $s_selected_position = 0;
8556
                    }
8557
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8558
                }
8559
            }
8560
        }
8561
8562
        if ('edit' === $action) {
8563
            if (true !== api_get_configuration_value('lp_item_prerequisite_dates')) {
8564
                $excludeExtraFields = array_merge($excludeExtraFields, ['start_date', 'end_date']);
8565
            }
8566
            $extraField = new ExtraField('lp_item');
8567
            $extraField->addElements($form, $id, $excludeExtraFields);
8568
        }
8569
8570
        if ($action == 'add') {
8571
            $form->addButtonSave(get_lang('AddForumToCourse'), 'submit_button');
8572
        } else {
8573
            $form->addButtonSave(get_lang('EditCurrentForum'), 'submit_button');
8574
        }
8575
8576
        if ($action == 'move') {
8577
            $form->addHidden('title', $item_title);
8578
            $form->addHidden('description', $item_description);
8579
        }
8580
8581
        if (is_numeric($extra_info)) {
8582
            $form->addHidden('path', $extra_info);
8583
        } elseif (is_array($extra_info)) {
8584
            $form->addHidden('path', $extra_info['path']);
8585
        }
8586
        $form->addHidden('type', TOOL_FORUM);
8587
        $form->addHidden('post_time', time());
8588
        $this->setAuthorLpItem($form);
8589
        $form->setDefaults($defaults);
8590
8591
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
8592
    }
8593
8594
    /**
8595
     * Return HTML form to add/edit forum threads.
8596
     *
8597
     * @param string $action
8598
     * @param int    $id         Item ID if already exists in learning path
8599
     * @param string $extra_info
8600
     *
8601
     * @throws Exception
8602
     *
8603
     * @return string HTML form
8604
     */
8605
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
8606
    {
8607
        $course_id = api_get_course_int_id();
8608
        if (empty($course_id)) {
8609
            return null;
8610
        }
8611
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
8612
8613
        $item_title = '';
8614
        $item_description = '';
8615
        if ($id != 0 && is_array($extra_info)) {
8616
            $item_title = stripslashes($extra_info['title']);
8617
        } elseif (is_numeric($extra_info)) {
8618
            $sql = "SELECT thread_title as title FROM $tbl_forum
8619
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
8620
8621
            $result = Database::query($sql);
8622
            $row = Database::fetch_array($result);
8623
8624
            $item_title = $row['title'];
8625
            $item_description = '';
8626
        }
8627
8628
        $parent = 0;
8629
        if ($id != 0 && is_array($extra_info)) {
8630
            $parent = $extra_info['parent_item_id'];
8631
        }
8632
8633
        $arrLP = $this->getItemsForForm();
8634
        $this->tree_array($arrLP);
8635
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8636
        unset($this->arrMenu);
8637
8638
        $form = new FormValidator(
8639
            'thread_form',
8640
            'POST',
8641
            $this->getCurrentBuildingModeURL()
8642
        );
8643
        $defaults = [];
8644
8645
        if ($action == 'add') {
8646
            $legend = get_lang('CreateTheForum');
8647
        } elseif ($action == 'move') {
8648
            $legend = get_lang('MoveTheCurrentForum');
8649
        } else {
8650
            $legend = get_lang('EditCurrentForum');
8651
        }
8652
8653
        $form->addHeader($legend);
8654
        $selectParent = $form->addSelect(
8655
            'parent',
8656
            get_lang('Parent'),
8657
            [],
8658
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
8659
        );
8660
        $selectParent->addOption($this->name, 0);
8661
8662
        $arrHide = [
8663
            $id,
8664
        ];
8665
8666
        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...
8667
            if ($action != 'add') {
8668
                if (
8669
                    ($arrLP[$i]['item_type'] == 'dir') &&
8670
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8671
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8672
                ) {
8673
                    $selectParent->addOption(
8674
                        $arrLP[$i]['title'],
8675
                        $arrLP[$i]['id'],
8676
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8677
                    );
8678
8679
                    if ($parent == $arrLP[$i]['id']) {
8680
                        $selectParent->setSelected($arrLP[$i]['id']);
8681
                    }
8682
                } else {
8683
                    $arrHide[] = $arrLP[$i]['id'];
8684
                }
8685
            } else {
8686
                if ($arrLP[$i]['item_type'] == 'dir') {
8687
                    $selectParent->addOption(
8688
                        $arrLP[$i]['title'],
8689
                        $arrLP[$i]['id'],
8690
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8691
                    );
8692
8693
                    if ($parent == $arrLP[$i]['id']) {
8694
                        $selectParent->setSelected($arrLP[$i]['id']);
8695
                    }
8696
                }
8697
            }
8698
        }
8699
8700
        if ($arrLP != null) {
8701
            reset($arrLP);
8702
        }
8703
8704
        $selectPrevious = $form->addSelect(
8705
            'previous',
8706
            get_lang('Position'),
8707
            [],
8708
            ['id' => 'previous']
8709
        );
8710
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8711
8712
        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...
8713
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8714
                $selectPrevious->addOption(
8715
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8716
                    $arrLP[$i]['id']
8717
                );
8718
8719
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8720
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8721
                } elseif ($action == 'add') {
8722
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8723
                }
8724
            }
8725
        }
8726
8727
        if ($action != 'move') {
8728
            $this->setItemTitle($form);
8729
            $defaults['title'] = $item_title;
8730
8731
            $id_prerequisite = 0;
8732
            if ($arrLP != null) {
8733
                foreach ($arrLP as $key => $value) {
8734
                    if ($value['id'] == $id) {
8735
                        $id_prerequisite = $value['prerequisite'];
8736
                        break;
8737
                    }
8738
                }
8739
            }
8740
8741
            $arrHide = [];
8742
            $s_selected_position = 0;
8743
            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...
8744
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8745
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8746
                        $s_selected_position = $arrLP[$i]['id'];
8747
                    } elseif ($action == 'add') {
8748
                        $s_selected_position = 0;
8749
                    }
8750
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8751
                }
8752
            }
8753
8754
            $selectPrerequisites = $form->addSelect(
8755
                'prerequisites',
8756
                get_lang('LearnpathPrerequisites'),
8757
                [],
8758
                ['id' => 'prerequisites']
8759
            );
8760
            $selectPrerequisites->addOption(get_lang('NoPrerequisites'), 0);
8761
8762
            foreach ($arrHide as $key => $value) {
8763
                $selectPrerequisites->addOption($value['value'], $key);
8764
8765
                if ($key == $s_selected_position && $action == 'add') {
8766
                    $selectPrerequisites->setSelected($key);
8767
                } elseif ($key == $id_prerequisite && $action == 'edit') {
8768
                    $selectPrerequisites->setSelected($key);
8769
                }
8770
            }
8771
        }
8772
8773
        if ('edit' === $action) {
8774
            $excludeExtraFields = [];
8775
            if (true !== api_get_configuration_value('lp_item_prerequisite_dates')) {
8776
                $excludeExtraFields = ['start_date', 'end_date'];
8777
            }
8778
            $extraField = new ExtraField('lp_item');
8779
            $extraField->addElements($form, $id, $excludeExtraFields);
8780
        }
8781
8782
        $form->addButtonSave(get_lang('Ok'), 'submit_button');
8783
8784
        if ($action == 'move') {
8785
            $form->addHidden('title', $item_title);
8786
            $form->addHidden('description', $item_description);
8787
        }
8788
8789
        if (is_numeric($extra_info)) {
8790
            $form->addHidden('path', $extra_info);
8791
        } elseif (is_array($extra_info)) {
8792
            $form->addHidden('path', $extra_info['path']);
8793
        }
8794
8795
        $form->addHidden('type', TOOL_THREAD);
8796
        $form->addHidden('post_time', time());
8797
        $this->setAuthorLpItem($form);
8798
        $form->setDefaults($defaults);
8799
8800
        return $form->returnForm();
8801
    }
8802
8803
    /**
8804
     * Return the HTML form to display an item (generally a dir item).
8805
     *
8806
     * @param string $item_type
8807
     * @param string $title
8808
     * @param string $action
8809
     * @param int    $id
8810
     * @param string $extra_info
8811
     *
8812
     * @throws Exception
8813
     * @throws HTML_QuickForm_Error
8814
     *
8815
     * @return string HTML form
8816
     */
8817
    public function display_item_form(
8818
        $item_type,
8819
        $title = '',
8820
        $action = 'add_item',
8821
        $id = 0,
8822
        $extra_info = 'new'
8823
    ) {
8824
        $_course = api_get_course_info();
8825
8826
        global $charset;
8827
8828
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8829
        $item_title = '';
8830
        $item_description = '';
8831
        $item_path_fck = '';
8832
8833
        $parent = 0;
8834
        $previousId = null;
8835
        if ($id != 0 && is_array($extra_info)) {
8836
            $item_title = $extra_info['title'];
8837
            $item_description = $extra_info['description'];
8838
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8839
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8840
            $parent = $extra_info['parent_item_id'];
8841
            $previousId = $extra_info['previous_item_id'];
8842
        }
8843
8844
        if ($extra_info instanceof learnpathItem) {
8845
            $item_title = $extra_info->get_title();
8846
            $item_description = $extra_info->get_description();
8847
            $path = $extra_info->get_path();
8848
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($path);
8849
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($path);
8850
            $parent = $extra_info->get_parent();
8851
            $previousId = $extra_info->previous;
8852
        }
8853
8854
        $id = (int) $id;
8855
        $sql = "SELECT * FROM $tbl_lp_item
8856
                WHERE
8857
                    lp_id = ".$this->lp_id." AND
8858
                    iid != $id";
8859
8860
        if ($item_type === 'dir') {
8861
            $sql .= " AND parent_item_id = 0";
8862
        }
8863
8864
        $result = Database::query($sql);
8865
        $arrLP = [];
8866
        while ($row = Database::fetch_array($result)) {
8867
            $arrLP[] = [
8868
                'id' => $row['iid'],
8869
                'item_type' => $row['item_type'],
8870
                'title' => $this->cleanItemTitle($row['title']),
8871
                'title_raw' => $row['title'],
8872
                'path' => $row['path'],
8873
                'description' => $row['description'],
8874
                'parent_item_id' => $row['parent_item_id'],
8875
                'previous_item_id' => $row['previous_item_id'],
8876
                'next_item_id' => $row['next_item_id'],
8877
                'max_score' => $row['max_score'],
8878
                'min_score' => $row['min_score'],
8879
                'mastery_score' => $row['mastery_score'],
8880
                'prerequisite' => $row['prerequisite'],
8881
                'display_order' => $row['display_order'],
8882
            ];
8883
        }
8884
8885
        $this->tree_array($arrLP);
8886
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8887
        unset($this->arrMenu);
8888
8889
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8890
8891
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
8892
        $defaults['title'] = api_html_entity_decode(
8893
            $item_title,
8894
            ENT_QUOTES,
8895
            $charset
8896
        );
8897
        $defaults['description'] = $item_description;
8898
8899
        $form->addHeader($title);
8900
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
8901
        $arrHide[0]['padding'] = 20;
8902
        $charset = api_get_system_encoding();
8903
        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...
8904
            if ($action != 'add') {
8905
                if ($arrLP[$i]['item_type'] === 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8906
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8907
                ) {
8908
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8909
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8910
                    if ($parent == $arrLP[$i]['id']) {
8911
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8912
                    }
8913
                }
8914
            } else {
8915
                if ($arrLP[$i]['item_type'] === 'dir') {
8916
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8917
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8918
                    if ($parent == $arrLP[$i]['id']) {
8919
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8920
                    }
8921
                }
8922
            }
8923
        }
8924
8925
        if ($action !== 'move') {
8926
            $this->setItemTitle($form);
8927
        } else {
8928
            $form->addElement('hidden', 'title');
8929
        }
8930
8931
        $parentSelect = $form->addElement(
8932
            'select',
8933
            'parent',
8934
            get_lang('Parent'),
8935
            '',
8936
            [
8937
                'id' => 'idParent',
8938
                'onchange' => 'javascript: load_cbo(this.value);',
8939
            ]
8940
        );
8941
8942
        foreach ($arrHide as $key => $value) {
8943
            $parentSelect->addOption(
8944
                $value['value'],
8945
                $key,
8946
                'style="padding-left:'.$value['padding'].'px;"'
8947
            );
8948
            $lastPosition = $key;
8949
        }
8950
8951
        if (!empty($s_selected_parent)) {
8952
            $parentSelect->setSelected($s_selected_parent);
8953
        }
8954
8955
        if (is_array($arrLP)) {
8956
            reset($arrLP);
8957
        }
8958
8959
        $arrHide = [];
8960
        // POSITION
8961
        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...
8962
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id &&
8963
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
8964
                //this is the same!
8965
                if (isset($previousId) && $previousId == $arrLP[$i]['id']) {
8966
                    $s_selected_position = $arrLP[$i]['id'];
8967
                } elseif ($action === 'add') {
8968
                    $s_selected_position = $arrLP[$i]['id'];
8969
                }
8970
8971
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8972
            }
8973
        }
8974
8975
        $position = $form->addElement(
8976
            'select',
8977
            'previous',
8978
            get_lang('Position'),
8979
            '',
8980
            ['id' => 'previous']
8981
        );
8982
        $padding = isset($value['padding']) ? $value['padding'] : 0;
8983
        $position->addOption(get_lang('FirstPosition'), 0, 'style="padding-left:'.$padding.'px;"');
8984
8985
        $lastPosition = null;
8986
        foreach ($arrHide as $key => $value) {
8987
            $position->addOption($value['value'], $key, 'style="padding-left:'.$padding.'px;"');
8988
            $lastPosition = $key;
8989
        }
8990
8991
        if (!empty($s_selected_position)) {
8992
            $position->setSelected($s_selected_position);
8993
        }
8994
8995
        // When new chapter add at the end
8996
        if ($action === 'add_item') {
8997
            $position->setSelected($lastPosition);
8998
        }
8999
9000
        if (is_array($arrLP)) {
9001
            reset($arrLP);
9002
        }
9003
9004
        $form->addButtonSave(get_lang('SaveSection'), 'submit_button');
9005
9006
        //fix in order to use the tab
9007
        if ($item_type === 'dir') {
9008
            $form->addElement('hidden', 'type', 'dir');
9009
        }
9010
9011
        $extension = null;
9012
        if (!empty($item_path)) {
9013
            $extension = pathinfo($item_path, PATHINFO_EXTENSION);
9014
        }
9015
9016
        //assets can't be modified
9017
        //$item_type == 'asset' ||
9018
        if (($item_type === 'sco') && ($extension === 'html' || $extension === 'htm')) {
9019
            if ($item_type === 'sco') {
9020
                $form->addElement(
9021
                    'html',
9022
                    '<script>alert("'.get_lang('WarningWhenEditingScorm').'")</script>'
9023
                );
9024
            }
9025
            $renderer = $form->defaultRenderer();
9026
            $renderer->setElementTemplate(
9027
                '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
9028
                'content_lp'
9029
            );
9030
9031
            $relative_prefix = '';
9032
            $editor_config = [
9033
                'ToolbarSet' => 'LearningPathDocuments',
9034
                'Width' => '100%',
9035
                'Height' => '500',
9036
                'FullPage' => true,
9037
                'CreateDocumentDir' => $relative_prefix,
9038
                'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
9039
                'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
9040
            ];
9041
9042
            $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
9043
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
9044
            $defaults['content_lp'] = file_get_contents($content_path);
9045
        }
9046
9047
        if (!empty($id)) {
9048
            $form->addHidden('id', $id);
9049
        }
9050
9051
        $form->addElement('hidden', 'type', $item_type);
9052
        $form->addElement('hidden', 'post_time', time());
9053
        $form->setDefaults($defaults);
9054
9055
        return $form->returnForm();
9056
    }
9057
9058
    /**
9059
     * @return string
9060
     */
9061
    public function getCurrentBuildingModeURL()
9062
    {
9063
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
9064
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
9065
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
9066
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
9067
9068
        $currentUrl = api_get_self().'?'.api_get_cidreq().
9069
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
9070
9071
        return $currentUrl;
9072
    }
9073
9074
    /**
9075
     * Returns the form to update or create a document.
9076
     *
9077
     * @param string        $action     (add/edit)
9078
     * @param int           $id         ID of the lp_item (if already exists)
9079
     * @param mixed         $extra_info Integer if document ID, string if info ('new')
9080
     * @param learnpathItem $item
9081
     *
9082
     * @return string HTML form
9083
     */
9084
    public function display_document_form(
9085
        $action = 'add',
9086
        $id = 0,
9087
        $extra_info = 'new',
9088
        $item = null,
9089
        $excludeExtraFields = []
9090
    ) {
9091
        $course_id = api_get_course_int_id();
9092
        $_course = api_get_course_info();
9093
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
9094
9095
        $no_display_edit_textarea = false;
9096
        //If action==edit document
9097
        //We don't display the document form if it's not an editable document (html or txt file)
9098
        if ($action === 'edit') {
9099
            if (is_array($extra_info)) {
9100
                $path_parts = pathinfo($extra_info['dir']);
9101
                if ($path_parts['extension'] !== 'txt' && $path_parts['extension'] !== 'html') {
9102
                    $no_display_edit_textarea = true;
9103
                }
9104
            }
9105
        }
9106
        $no_display_add = false;
9107
        // If action==add an existing document
9108
        // We don't display the document form if it's not an editable document (html or txt file).
9109
        if ($action === 'add') {
9110
            if (is_numeric($extra_info)) {
9111
                $extra_info = (int) $extra_info;
9112
                $sql_doc = "SELECT path FROM $tbl_doc
9113
                            WHERE c_id = $course_id AND iid = ".$extra_info;
9114
                $result = Database::query($sql_doc);
9115
                $path_file = Database::result($result, 0, 0);
9116
                $path_parts = pathinfo($path_file);
9117
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
9118
                    $no_display_add = true;
9119
                }
9120
            }
9121
        }
9122
9123
        $item_title = '';
9124
        $item_description = '';
9125
        if ($id != 0 && is_array($extra_info)) {
9126
            $item_title = stripslashes($extra_info['title']);
9127
            $item_description = stripslashes($extra_info['description']);
9128
            if (empty($item_title)) {
9129
                $path_parts = pathinfo($extra_info['path']);
9130
                $item_title = stripslashes($path_parts['filename']);
9131
            }
9132
        } elseif (is_numeric($extra_info)) {
9133
            $sql = "SELECT path, title FROM $tbl_doc
9134
                    WHERE
9135
                        c_id = ".$course_id." AND
9136
                        iid = ".intval($extra_info);
9137
            $result = Database::query($sql);
9138
            $row = Database::fetch_array($result);
9139
            $item_title = $row['title'];
9140
            $item_title = str_replace('_', ' ', $item_title);
9141
            if (empty($item_title)) {
9142
                $path_parts = pathinfo($row['path']);
9143
                $item_title = stripslashes($path_parts['filename']);
9144
            }
9145
        }
9146
9147
        $return = '<legend>';
9148
        $parent = 0;
9149
        if ($id != 0 && is_array($extra_info)) {
9150
            $parent = $extra_info['parent_item_id'];
9151
        }
9152
9153
        $selectedPosition = 0;
9154
        if (is_array($extra_info) && isset($extra_info['previous_item_id'])) {
9155
            $selectedPosition = $extra_info['previous_item_id'];
9156
        }
9157
9158
        if ($item instanceof learnpathItem) {
9159
            $item_title = stripslashes($item->get_title());
9160
            $item_description = stripslashes($item->get_description());
9161
            $selectedPosition = $item->previous;
9162
            $parent = $item->parent;
9163
        }
9164
9165
        $arrLP = $this->getItemsForForm();
9166
        $this->tree_array($arrLP);
9167
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9168
        unset($this->arrMenu);
9169
9170
        if ($action === 'add') {
9171
            $return .= get_lang('CreateTheDocument');
9172
        } elseif ($action === 'move') {
9173
            $return .= get_lang('MoveTheCurrentDocument');
9174
        } else {
9175
            $return .= get_lang('EditTheCurrentDocument');
9176
        }
9177
        $return .= '</legend>';
9178
9179
        if (isset($_GET['edit']) && $_GET['edit'] === 'true') {
9180
            $return .= Display::return_message(
9181
                '<strong>'.get_lang('Warning').' !</strong><br />'.get_lang('WarningEditingDocument'),
9182
                false
9183
            );
9184
        }
9185
        $form = new FormValidator(
9186
            'form',
9187
            'POST',
9188
            $this->getCurrentBuildingModeURL(),
9189
            '',
9190
            ['enctype' => 'multipart/form-data']
9191
        );
9192
        $defaults['title'] = Security::remove_XSS($item_title);
9193
        if (empty($item_title)) {
9194
            $defaults['title'] = Security::remove_XSS($item_title);
9195
        }
9196
        $defaults['description'] = $item_description;
9197
        $form->addElement('html', $return);
9198
9199
        if ($action !== 'move') {
9200
            $data = $this->generate_lp_folder($_course);
9201
            if ($action !== 'edit') {
9202
                $folders = DocumentManager::get_all_document_folders(
9203
                    $_course,
9204
                    0,
9205
                    true
9206
                );
9207
                DocumentManager::build_directory_selector(
9208
                    $folders,
9209
                    '',
9210
                    [],
9211
                    true,
9212
                    $form,
9213
                    'directory_parent_id'
9214
                );
9215
            }
9216
9217
            if (isset($data['id'])) {
9218
                $defaults['directory_parent_id'] = $data['id'];
9219
            }
9220
            $this->setItemTitle($form);
9221
        }
9222
9223
        $arrHide[0]['value'] = $this->name;
9224
        $arrHide[0]['padding'] = 20;
9225
9226
        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...
9227
            if ($action !== 'add') {
9228
                if ($arrLP[$i]['item_type'] === 'dir' &&
9229
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9230
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9231
                ) {
9232
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9233
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9234
                }
9235
            } else {
9236
                if ($arrLP[$i]['item_type'] == 'dir') {
9237
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9238
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9239
                }
9240
            }
9241
        }
9242
9243
        $parentSelect = $form->addSelect(
9244
            'parent',
9245
            get_lang('Parent'),
9246
            [],
9247
            [
9248
                'id' => 'idParent',
9249
                'onchange' => 'javascript: load_cbo(this.value);',
9250
            ]
9251
        );
9252
9253
        $my_count = 0;
9254
        foreach ($arrHide as $key => $value) {
9255
            if ($my_count != 0) {
9256
                // The LP name is also the first section and is not in the same charset like the other sections.
9257
                $value['value'] = Security::remove_XSS($value['value']);
9258
                $parentSelect->addOption(
9259
                    $value['value'],
9260
                    $key,
9261
                    'style="padding-left:'.$value['padding'].'px;"'
9262
                );
9263
            } else {
9264
                $value['value'] = Security::remove_XSS($value['value']);
9265
                $parentSelect->addOption(
9266
                    $value['value'],
9267
                    $key,
9268
                    'style="padding-left:'.$value['padding'].'px;"'
9269
                );
9270
            }
9271
            $my_count++;
9272
        }
9273
9274
        if (!empty($id)) {
9275
            $parentSelect->setSelected($parent);
9276
        } else {
9277
            $parent_item_id = Session::read('parent_item_id', 0);
9278
            $parentSelect->setSelected($parent_item_id);
9279
        }
9280
9281
        if (is_array($arrLP)) {
9282
            reset($arrLP);
9283
        }
9284
9285
        $arrHide = [];
9286
        // POSITION
9287
        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...
9288
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
9289
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
9290
            ) {
9291
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
9292
            }
9293
        }
9294
9295
        $position = $form->addSelect(
9296
            'previous',
9297
            get_lang('Position'),
9298
            [],
9299
            ['id' => 'previous']
9300
        );
9301
9302
        $position->addOption(get_lang('FirstPosition'), 0);
9303
        foreach ($arrHide as $key => $value) {
9304
            $padding = isset($value['padding']) ? $value['padding'] : 20;
9305
            $position->addOption(
9306
                $value['value'],
9307
                $key,
9308
                'style="padding-left:'.$padding.'px;"'
9309
            );
9310
        }
9311
        $position->setSelected($selectedPosition);
9312
9313
        if (is_array($arrLP)) {
9314
            reset($arrLP);
9315
        }
9316
9317
        if (true !== api_get_configuration_value('lp_item_prerequisite_dates')) {
9318
            $excludeExtraFields = array_merge($excludeExtraFields, ['start_date', 'end_date']);
9319
        }
9320
        $extraField = new ExtraField('lp_item');
9321
        $extraField->addElements($form, $id, $excludeExtraFields);
9322
9323
        if ($action !== 'move') {
9324
            $arrHide = [];
9325
            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...
9326
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] !== 'dir' &&
9327
                    $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9328
                ) {
9329
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9330
                }
9331
            }
9332
9333
            if (!$no_display_add) {
9334
                $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
9335
                $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
9336
                if ($extra_info === 'new' || $item_type == TOOL_DOCUMENT ||
9337
                    $item_type == TOOL_LP_FINAL_ITEM || $edit === 'true'
9338
                ) {
9339
                    if (isset($_POST['content'])) {
9340
                        $content = stripslashes($_POST['content']);
9341
                    } elseif (is_array($extra_info)) {
9342
                        //If it's an html document or a text file
9343
                        if (!$no_display_edit_textarea) {
9344
                            $content = $this->display_document(
9345
                                $extra_info['path'],
9346
                                false,
9347
                                false
9348
                            );
9349
                        }
9350
                    } elseif (is_numeric($extra_info)) {
9351
                        $content = $this->display_document(
9352
                            $extra_info,
9353
                            false,
9354
                            false
9355
                        );
9356
                    } else {
9357
                        $content = '';
9358
                    }
9359
9360
                    if (!$no_display_edit_textarea) {
9361
                        // We need to calculate here some specific settings for the online editor.
9362
                        // The calculated settings work for documents in the Documents tool
9363
                        // (on the root or in subfolders).
9364
                        // For documents in native scorm packages it is unclear whether the
9365
                        // online editor should be activated or not.
9366
9367
                        // A new document, it is in the root of the repository.
9368
                        if (is_array($extra_info) && $extra_info != 'new') {
9369
                            // The document already exists. Whe have to determine its relative path towards the repository root.
9370
                            $relative_path = explode('/', $extra_info['dir']);
9371
                            $cnt = count($relative_path) - 2;
9372
                            if ($cnt < 0) {
9373
                                $cnt = 0;
9374
                            }
9375
                            $relative_prefix = str_repeat('../', $cnt);
9376
                            $relative_path = array_slice($relative_path, 1, $cnt);
9377
                            $relative_path = implode('/', $relative_path);
9378
                            if (strlen($relative_path) > 0) {
9379
                                $relative_path = $relative_path.'/';
9380
                            }
9381
                        } else {
9382
                            $result = $this->generate_lp_folder($_course);
9383
                            $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
9384
                            $relative_prefix = '../../';
9385
                        }
9386
9387
                        $editor_config = [
9388
                            'ToolbarSet' => 'LearningPathDocuments',
9389
                            'Width' => '100%',
9390
                            'Height' => '500',
9391
                            'FullPage' => true,
9392
                            'CreateDocumentDir' => $relative_prefix,
9393
                            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
9394
                            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
9395
                        ];
9396
9397
                        if ($_GET['action'] === 'add_item') {
9398
                            $class = 'add';
9399
                            $text = get_lang('LPCreateDocument');
9400
                        } else {
9401
                            if ($_GET['action'] === 'edit_item') {
9402
                                $class = 'save';
9403
                                $text = get_lang('SaveDocument');
9404
                            }
9405
                        }
9406
9407
                        $form->addButtonSave($text, 'submit_button');
9408
                        $renderer = $form->defaultRenderer();
9409
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
9410
                        $form->addElement('html', '<div class="editor-lp">');
9411
                        $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
9412
                        $form->addElement('html', '</div>');
9413
                        $defaults['content_lp'] = $content;
9414
                    }
9415
                } elseif (is_numeric($extra_info)) {
9416
                    $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9417
9418
                    $return = $this->display_document($extra_info, true, true, true);
9419
                    $form->addElement('html', $return);
9420
                }
9421
            }
9422
        }
9423
        if (isset($extra_info['item_type']) &&
9424
            $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
9425
        ) {
9426
            $parentSelect->freeze();
9427
            $position->freeze();
9428
        }
9429
9430
        if ($action === 'move') {
9431
            $form->addElement('hidden', 'title', $item_title);
9432
            $form->addElement('hidden', 'description', $item_description);
9433
        }
9434
        if (is_numeric($extra_info)) {
9435
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9436
            $form->addElement('hidden', 'path', $extra_info);
9437
        } elseif (is_array($extra_info)) {
9438
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9439
            $form->addElement('hidden', 'path', $extra_info['path']);
9440
        }
9441
9442
        if ($item instanceof learnpathItem) {
9443
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9444
            $form->addElement('hidden', 'path', $item->path);
9445
        }
9446
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
9447
        $form->addElement('hidden', 'post_time', time());
9448
        $this->setAuthorLpItem($form);
9449
        $form->setDefaults($defaults);
9450
9451
        return $form->returnForm();
9452
    }
9453
9454
    /**
9455
     * Returns the form to update or create a read-out text.
9456
     *
9457
     * @param string $action     "add" or "edit"
9458
     * @param int    $id         ID of the lp_item (if already exists)
9459
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
9460
     *
9461
     * @throws Exception
9462
     * @throws HTML_QuickForm_Error
9463
     *
9464
     * @return string HTML form
9465
     */
9466
    public function displayFrmReadOutText($action = 'add', $id = 0, $extra_info = 'new')
9467
    {
9468
        $course_id = api_get_course_int_id();
9469
        $_course = api_get_course_info();
9470
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
9471
9472
        $no_display_edit_textarea = false;
9473
        //If action==edit document
9474
        //We don't display the document form if it's not an editable document (html or txt file)
9475
        if ($action == 'edit') {
9476
            if (is_array($extra_info)) {
9477
                $path_parts = pathinfo($extra_info['dir']);
9478
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
9479
                    $no_display_edit_textarea = true;
9480
                }
9481
            }
9482
        }
9483
        $no_display_add = false;
9484
9485
        $item_title = '';
9486
        $item_description = '';
9487
        if ($id != 0 && is_array($extra_info)) {
9488
            $item_title = stripslashes($extra_info['title']);
9489
            $item_description = stripslashes($extra_info['description']);
9490
            $item_terms = stripslashes($extra_info['terms']);
9491
            if (empty($item_title)) {
9492
                $path_parts = pathinfo($extra_info['path']);
9493
                $item_title = stripslashes($path_parts['filename']);
9494
            }
9495
        } elseif (is_numeric($extra_info)) {
9496
            $sql = "SELECT path, title FROM $tbl_doc WHERE c_id = ".$course_id." AND iid = ".intval($extra_info);
9497
            $result = Database::query($sql);
9498
            $row = Database::fetch_array($result);
9499
            $item_title = $row['title'];
9500
            $item_title = str_replace('_', ' ', $item_title);
9501
            if (empty($item_title)) {
9502
                $path_parts = pathinfo($row['path']);
9503
                $item_title = stripslashes($path_parts['filename']);
9504
            }
9505
        }
9506
9507
        $parent = 0;
9508
        if ($id != 0 && is_array($extra_info)) {
9509
            $parent = $extra_info['parent_item_id'];
9510
        }
9511
9512
        $arrLP = $this->getItemsForForm();
9513
        $this->tree_array($arrLP);
9514
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9515
        unset($this->arrMenu);
9516
9517
        if ($action === 'add') {
9518
            $formHeader = get_lang('CreateTheDocument');
9519
        } else {
9520
            $formHeader = get_lang('EditTheCurrentDocument');
9521
        }
9522
9523
        if ('edit' === $action) {
9524
            $urlAudioIcon = Display::url(
9525
                Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY),
9526
                api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'&'
9527
                    .http_build_query(['view' => 'build', 'id' => $id, 'action' => 'add_audio'])
9528
            );
9529
        } else {
9530
            $urlAudioIcon = Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY);
9531
        }
9532
9533
        $form = new FormValidator(
9534
            'frm_add_reading',
9535
            'POST',
9536
            $this->getCurrentBuildingModeURL(),
9537
            '',
9538
            ['enctype' => 'multipart/form-data']
9539
        );
9540
        $form->addHeader($formHeader);
9541
        $form->addHtml(
9542
            Display::return_message(
9543
                sprintf(get_lang('FrmReadOutTextIntro'), $urlAudioIcon),
9544
                'normal',
9545
                false
9546
            )
9547
        );
9548
        $defaults['title'] = !empty($item_title) ? Security::remove_XSS($item_title) : '';
9549
        $defaults['description'] = $item_description;
9550
9551
        $data = $this->generate_lp_folder($_course);
9552
9553
        if ($action != 'edit') {
9554
            $folders = DocumentManager::get_all_document_folders($_course, 0, true);
9555
            DocumentManager::build_directory_selector(
9556
                $folders,
9557
                '',
9558
                [],
9559
                true,
9560
                $form,
9561
                'directory_parent_id'
9562
            );
9563
        }
9564
9565
        if (isset($data['id'])) {
9566
            $defaults['directory_parent_id'] = $data['id'];
9567
        }
9568
        $this->setItemTitle($form);
9569
9570
        $arrHide[0]['value'] = $this->name;
9571
        $arrHide[0]['padding'] = 20;
9572
9573
        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...
9574
            if ($action != 'add') {
9575
                if ($arrLP[$i]['item_type'] == 'dir' &&
9576
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9577
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9578
                ) {
9579
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9580
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9581
                }
9582
            } else {
9583
                if ($arrLP[$i]['item_type'] == 'dir') {
9584
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9585
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9586
                }
9587
            }
9588
        }
9589
9590
        $parent_select = $form->addSelect(
9591
            'parent',
9592
            get_lang('Parent'),
9593
            [],
9594
            ['onchange' => "javascript: load_cbo(this.value, 'frm_add_reading_previous');"]
9595
        );
9596
9597
        $my_count = 0;
9598
        foreach ($arrHide as $key => $value) {
9599
            if ($my_count != 0) {
9600
                // The LP name is also the first section and is not in the same charset like the other sections.
9601
                $value['value'] = Security::remove_XSS($value['value']);
9602
                $parent_select->addOption(
9603
                    $value['value'],
9604
                    $key,
9605
                    'style="padding-left:'.$value['padding'].'px;"'
9606
                );
9607
            } else {
9608
                $value['value'] = Security::remove_XSS($value['value']);
9609
                $parent_select->addOption(
9610
                    $value['value'],
9611
                    $key,
9612
                    'style="padding-left:'.$value['padding'].'px;"'
9613
                );
9614
            }
9615
            $my_count++;
9616
        }
9617
9618
        if (!empty($id)) {
9619
            $parent_select->setSelected($parent);
9620
        } else {
9621
            $parent_item_id = Session::read('parent_item_id', 0);
9622
            $parent_select->setSelected($parent_item_id);
9623
        }
9624
9625
        if (is_array($arrLP)) {
9626
            reset($arrLP);
9627
        }
9628
9629
        $arrHide = [];
9630
        $s_selected_position = null;
9631
9632
        // POSITION
9633
        $lastPosition = null;
9634
9635
        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...
9636
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) &&
9637
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9638
            ) {
9639
                if ((isset($extra_info['previous_item_id']) &&
9640
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
9641
                ) {
9642
                    $s_selected_position = $arrLP[$i]['id'];
9643
                }
9644
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
9645
            }
9646
            $lastPosition = $arrLP[$i]['id'];
9647
        }
9648
9649
        if (empty($s_selected_position)) {
9650
            $s_selected_position = $lastPosition;
9651
        }
9652
9653
        $position = $form->addSelect(
9654
            'previous',
9655
            get_lang('Position'),
9656
            []
9657
        );
9658
        $position->addOption(get_lang('FirstPosition'), 0);
9659
9660
        foreach ($arrHide as $key => $value) {
9661
            $padding = isset($value['padding']) ? $value['padding'] : 20;
9662
            $position->addOption(
9663
                $value['value'],
9664
                $key,
9665
                'style="padding-left:'.$padding.'px;"'
9666
            );
9667
        }
9668
        $position->setSelected($s_selected_position);
9669
9670
        if (is_array($arrLP)) {
9671
            reset($arrLP);
9672
        }
9673
9674
        if ('edit' === $action) {
9675
            $excludeExtraFields = [];
9676
            if (true !== api_get_configuration_value('lp_item_prerequisite_dates')) {
9677
                $excludeExtraFields = ['start_date', 'end_date'];
9678
            }
9679
            $extraField = new ExtraField('lp_item');
9680
            $extraField->addElements($form, $id, $excludeExtraFields);
9681
        }
9682
9683
        $arrHide = [];
9684
9685
        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...
9686
            if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
9687
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9688
            ) {
9689
                $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9690
            }
9691
        }
9692
9693
        if (!$no_display_add) {
9694
            $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
9695
            $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
9696
9697
            if ($extra_info == 'new' || $item_type == TOOL_READOUT_TEXT || $edit == 'true') {
9698
                if (!$no_display_edit_textarea) {
9699
                    $content = '';
9700
9701
                    if (isset($_POST['content'])) {
9702
                        $content = stripslashes($_POST['content']);
9703
                    } elseif (is_array($extra_info)) {
9704
                        $content = $this->display_document($extra_info['path'], false, false);
9705
                    } elseif (is_numeric($extra_info)) {
9706
                        $content = $this->display_document($extra_info, false, false);
9707
                    }
9708
9709
                    // A new document, it is in the root of the repository.
9710
                    if (is_array($extra_info) && $extra_info != 'new') {
9711
                    } else {
9712
                        $this->generate_lp_folder($_course);
9713
                    }
9714
9715
                    if ($_GET['action'] == 'add_item') {
9716
                        $text = get_lang('LPCreateDocument');
9717
                    } else {
9718
                        $text = get_lang('SaveDocument');
9719
                    }
9720
9721
                    $form->addTextarea('content_lp', get_lang('Content'), ['rows' => 20]);
9722
                    $form
9723
                        ->defaultRenderer()
9724
                        ->setElementTemplate($form->getDefaultElementTemplate(), 'content_lp');
9725
                    $form->addButtonSave($text, 'submit_button');
9726
                    $defaults['content_lp'] = $content;
9727
                }
9728
            } elseif (is_numeric($extra_info)) {
9729
                $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9730
9731
                $return = $this->display_document($extra_info, true, true, true);
9732
                $form->addElement('html', $return);
9733
            }
9734
        }
9735
9736
        if (is_numeric($extra_info)) {
9737
            $form->addElement('hidden', 'path', $extra_info);
9738
        } elseif (is_array($extra_info)) {
9739
            $form->addElement('hidden', 'path', $extra_info['path']);
9740
        }
9741
9742
        $form->addElement('hidden', 'type', TOOL_READOUT_TEXT);
9743
        $form->addElement('hidden', 'post_time', time());
9744
        $this->setAuthorLpItem($form);
9745
        $form->setDefaults($defaults);
9746
9747
        return $form->returnForm();
9748
    }
9749
9750
    /**
9751
     * @param array  $courseInfo
9752
     * @param string $content
9753
     * @param string $title
9754
     * @param int    $parentId
9755
     *
9756
     * @throws \Doctrine\ORM\ORMException
9757
     * @throws \Doctrine\ORM\OptimisticLockException
9758
     * @throws \Doctrine\ORM\TransactionRequiredException
9759
     *
9760
     * @return int
9761
     */
9762
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
9763
    {
9764
        $creatorId = api_get_user_id();
9765
        $sessionId = api_get_session_id();
9766
9767
        // Generates folder
9768
        $result = $this->generate_lp_folder($courseInfo);
9769
        $dir = $result['dir'];
9770
9771
        if (empty($parentId) || $parentId == '/') {
9772
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
9773
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
9774
9775
            if ($parentId === '/') {
9776
                $dir = '/';
9777
            }
9778
9779
            // Please, do not modify this dirname formatting.
9780
            if (strstr($dir, '..')) {
9781
                $dir = '/';
9782
            }
9783
9784
            if (!empty($dir[0]) && $dir[0] == '.') {
9785
                $dir = substr($dir, 1);
9786
            }
9787
            if (!empty($dir[0]) && $dir[0] != '/') {
9788
                $dir = '/'.$dir;
9789
            }
9790
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
9791
                $dir .= '/';
9792
            }
9793
        } else {
9794
            $parentInfo = DocumentManager::get_document_data_by_id(
9795
                $parentId,
9796
                $courseInfo['code']
9797
            );
9798
            if (!empty($parentInfo)) {
9799
                $dir = $parentInfo['path'].'/';
9800
            }
9801
        }
9802
9803
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9804
9805
        if (!is_dir($filepath)) {
9806
            $dir = '/';
9807
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9808
        }
9809
9810
        $originalTitle = !empty($title) ? $title : $_POST['title'];
9811
9812
        if (!empty($title)) {
9813
            $title = api_replace_dangerous_char(stripslashes($title));
9814
        } else {
9815
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
9816
        }
9817
9818
        $title = disable_dangerous_file($title);
9819
        $filename = $title;
9820
        $content = !empty($content) ? $content : $_POST['content_lp'];
9821
        $tmpFileName = $filename;
9822
9823
        $i = 0;
9824
        while (file_exists($filepath.$tmpFileName.'.html')) {
9825
            $tmpFileName = $filename.'_'.++$i;
9826
        }
9827
9828
        $filename = $tmpFileName.'.html';
9829
        $content = stripslashes($content);
9830
9831
        if (file_exists($filepath.$filename)) {
9832
            return 0;
9833
        }
9834
9835
        $putContent = file_put_contents($filepath.$filename, $content);
9836
9837
        if ($putContent === false) {
9838
            return 0;
9839
        }
9840
9841
        $fileSize = filesize($filepath.$filename);
9842
        $saveFilePath = $dir.$filename;
9843
9844
        $documentId = add_document(
9845
            $courseInfo,
9846
            $saveFilePath,
9847
            'file',
9848
            $fileSize,
9849
            $tmpFileName,
9850
            '',
9851
            0, //readonly
9852
            true,
9853
            null,
9854
            $sessionId,
9855
            $creatorId
9856
        );
9857
9858
        if (!$documentId) {
9859
            return 0;
9860
        }
9861
9862
        api_item_property_update(
9863
            $courseInfo,
9864
            TOOL_DOCUMENT,
9865
            $documentId,
9866
            'DocumentAdded',
9867
            $creatorId,
9868
            null,
9869
            null,
9870
            null,
9871
            null,
9872
            $sessionId
9873
        );
9874
9875
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
9876
        $newTitle = $originalTitle;
9877
9878
        if ($newComment || $newTitle) {
9879
            $em = Database::getManager();
9880
9881
            /** @var CDocument $doc */
9882
            $doc = $em->find('ChamiloCourseBundle:CDocument', $documentId);
9883
9884
            if ($newComment) {
9885
                $doc->setComment($newComment);
9886
            }
9887
9888
            if ($newTitle) {
9889
                $doc->setTitle($newTitle);
9890
            }
9891
9892
            $em->persist($doc);
9893
            $em->flush();
9894
        }
9895
9896
        return $documentId;
9897
    }
9898
9899
    /**
9900
     * Return HTML form to add/edit a link item.
9901
     *
9902
     * @param string $action     (add/edit)
9903
     * @param int    $id         Item ID if exists
9904
     * @param mixed  $extra_info
9905
     *
9906
     * @throws Exception
9907
     * @throws HTML_QuickForm_Error
9908
     *
9909
     * @return string HTML form
9910
     */
9911
    public function display_link_form(
9912
        $action = 'add',
9913
        $id = 0,
9914
        $extra_info = '',
9915
        $item = null,
9916
        $excludeExtraFields = []
9917
    ) {
9918
        $course_id = api_get_course_int_id();
9919
        $tbl_link = Database::get_course_table(TABLE_LINK);
9920
9921
        $item_title = '';
9922
        $item_description = '';
9923
        $item_url = '';
9924
9925
        $previousId = 0;
9926
        if ($id != 0 && is_array($extra_info)) {
9927
            $item_title = stripslashes($extra_info['title']);
9928
            $item_description = stripslashes($extra_info['description']);
9929
            $item_url = stripslashes($extra_info['url']);
9930
            $previousId = $extra_info['previous_item_id'];
9931
        } elseif (is_numeric($extra_info)) {
9932
            $extra_info = (int) $extra_info;
9933
            $sql = "SELECT title, description, url
9934
                    FROM $tbl_link
9935
                    WHERE c_id = $course_id AND iid = $extra_info";
9936
            $result = Database::query($sql);
9937
            $row = Database::fetch_array($result);
9938
            $item_title = $row['title'];
9939
            $item_description = $row['description'];
9940
            $item_url = $row['url'];
9941
        }
9942
9943
        if ($item instanceof learnpathItem) {
9944
            $previousId = $extra_info->previous;
9945
        }
9946
9947
        $form = new FormValidator(
9948
            'edit_link',
9949
            'POST',
9950
            $this->getCurrentBuildingModeURL()
9951
        );
9952
        $defaults = [];
9953
        $parent = 0;
9954
        if ($id != 0 && is_array($extra_info)) {
9955
            $parent = $extra_info['parent_item_id'];
9956
        }
9957
9958
        $arrLP = $this->getItemsForForm();
9959
9960
        $this->tree_array($arrLP);
9961
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9962
        unset($this->arrMenu);
9963
9964
        if ($action === 'add') {
9965
            $legend = get_lang('CreateTheLink');
9966
        } elseif ($action === 'move') {
9967
            $legend = get_lang('MoveCurrentLink');
9968
        } else {
9969
            $legend = get_lang('EditCurrentLink');
9970
        }
9971
9972
        $form->addHeader($legend);
9973
9974
        if ($action !== 'move') {
9975
            $this->setItemTitle($form);
9976
            $defaults['title'] = $item_title;
9977
        }
9978
9979
        $selectParent = $form->addSelect(
9980
            'parent',
9981
            get_lang('Parent'),
9982
            [],
9983
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
9984
        );
9985
        $selectParent->addOption($this->name, 0);
9986
        $arrHide = [
9987
            $id,
9988
        ];
9989
9990
        $parent_item_id = Session::read('parent_item_id', 0);
9991
9992
        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...
9993
            if ($action != 'add') {
9994
                if (
9995
                    ($arrLP[$i]['item_type'] == 'dir') &&
9996
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9997
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9998
                ) {
9999
                    $selectParent->addOption(
10000
                        $arrLP[$i]['title'],
10001
                        $arrLP[$i]['id'],
10002
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
10003
                    );
10004
10005
                    if ($parent == $arrLP[$i]['id']) {
10006
                        $selectParent->setSelected($arrLP[$i]['id']);
10007
                    }
10008
                } else {
10009
                    $arrHide[] = $arrLP[$i]['id'];
10010
                }
10011
            } else {
10012
                if ($arrLP[$i]['item_type'] == 'dir') {
10013
                    $selectParent->addOption(
10014
                        $arrLP[$i]['title'],
10015
                        $arrLP[$i]['id'],
10016
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
10017
                    );
10018
10019
                    if ($parent_item_id == $arrLP[$i]['id']) {
10020
                        $selectParent->setSelected($arrLP[$i]['id']);
10021
                    }
10022
                }
10023
            }
10024
        }
10025
10026
        if (is_array($arrLP)) {
10027
            reset($arrLP);
10028
        }
10029
10030
        $selectPrevious = $form->addSelect(
10031
            'previous',
10032
            get_lang('Position'),
10033
            [],
10034
            ['id' => 'previous', 'class' => 'learnpath_item_form']
10035
        );
10036
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
10037
10038
        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...
10039
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
10040
                $selectPrevious->addOption(
10041
                    $arrLP[$i]['title'],
10042
                    $arrLP[$i]['id']
10043
                );
10044
10045
                if ($previousId == $arrLP[$i]['id']) {
10046
                    $selectPrevious->setSelected($arrLP[$i]['id']);
10047
                } elseif ($action === 'add') {
10048
                    $selectPrevious->setSelected($arrLP[$i]['id']);
10049
                }
10050
            }
10051
        }
10052
10053
        if ($action !== 'move') {
10054
            $urlAttributes = ['class' => 'learnpath_item_form'];
10055
10056
            if (is_numeric($extra_info)) {
10057
                $urlAttributes['disabled'] = 'disabled';
10058
            }
10059
10060
            $form->addElement('url', 'url', get_lang('Url'), $urlAttributes);
10061
            $defaults['url'] = $item_url;
10062
            $arrHide = [];
10063
            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...
10064
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
10065
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
10066
                }
10067
            }
10068
        }
10069
10070
        if ('edit' === $action) {
10071
            if (true !== api_get_configuration_value('lp_item_prerequisite_dates')) {
10072
                $excludeExtraFields = array_merge($excludeExtraFields, ['start_date', 'end_date']);
10073
            }
10074
            $extraField = new ExtraField('lp_item');
10075
            $extraField->addElements($form, $id, $excludeExtraFields);
10076
        }
10077
10078
        if ($action === 'add') {
10079
            $form->addButtonSave(get_lang('AddLinkToCourse'), 'submit_button');
10080
        } else {
10081
            $form->addButtonSave(get_lang('EditCurrentLink'), 'submit_button');
10082
        }
10083
10084
        if ($action === 'move') {
10085
            $form->addHidden('title', $item_title);
10086
            $form->addHidden('description', $item_description);
10087
        }
10088
10089
        if (is_numeric($extra_info)) {
10090
            $form->addHidden('path', $extra_info);
10091
        } elseif (is_array($extra_info)) {
10092
            $form->addHidden('path', $extra_info['path']);
10093
        }
10094
        $form->addHidden('type', TOOL_LINK);
10095
        $form->addHidden('post_time', time());
10096
        $this->setAuthorLpItem($form);
10097
        $form->setDefaults($defaults);
10098
10099
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
10100
    }
10101
10102
    /**
10103
     * Return HTML form to add/edit a student publication (work).
10104
     *
10105
     * @param string $action
10106
     * @param int    $id         Item ID if already exists
10107
     * @param string $extra_info
10108
     *
10109
     * @throws Exception
10110
     *
10111
     * @return string HTML form
10112
     */
10113
    public function display_student_publication_form(
10114
        $action = 'add',
10115
        $id = 0,
10116
        $extra_info = '',
10117
        $item = null,
10118
        $excludeExtraFields = []
10119
    ) {
10120
        $course_id = api_get_course_int_id();
10121
        $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
10122
10123
        $item_title = get_lang('Student_publication');
10124
        $previousId = 0;
10125
        if ($id != 0 && is_array($extra_info)) {
10126
            $item_title = stripslashes($extra_info['title']);
10127
            $item_description = stripslashes($extra_info['description']);
10128
            $previousId = $extra_info['previous_item_id'];
10129
        } elseif (is_numeric($extra_info)) {
10130
            $extra_info = (int) $extra_info;
10131
            $sql = "SELECT title, description
10132
                    FROM $tbl_publication
10133
                    WHERE c_id = $course_id AND id = ".$extra_info;
10134
10135
            $result = Database::query($sql);
10136
            $row = Database::fetch_array($result);
10137
            if ($row) {
10138
                $item_title = $row['title'];
10139
            }
10140
        }
10141
10142
        if ($item instanceof learnpathItem) {
10143
            $item_title = $item->get_title();
10144
            $item_description = $item->get_description();
10145
            $previousId = $item->previous;
10146
        }
10147
10148
        $parent = 0;
10149
        if ($id != 0 && is_array($extra_info)) {
10150
            $parent = $extra_info['parent_item_id'];
10151
        }
10152
10153
        $arrLP = $this->getItemsForForm();
10154
10155
        $this->tree_array($arrLP);
10156
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
10157
        unset($this->arrMenu);
10158
10159
        $form = new FormValidator('frm_student_publication', 'post', '#');
10160
10161
        if ($action === 'add') {
10162
            $form->addHeader(get_lang('Student_publication'));
10163
        } elseif ($action === 'move') {
10164
            $form->addHeader(get_lang('MoveCurrentStudentPublication'));
10165
        } else {
10166
            $form->addHeader(get_lang('EditCurrentStudentPublication'));
10167
        }
10168
10169
        if ($action !== 'move') {
10170
            $this->setItemTitle($form);
10171
        }
10172
10173
        $parentSelect = $form->addSelect(
10174
            'parent',
10175
            get_lang('Parent'),
10176
            ['0' => $this->name],
10177
            [
10178
                'onchange' => 'javascript: load_cbo(this.value);',
10179
                'class' => 'learnpath_item_form',
10180
                'id' => 'idParent',
10181
            ]
10182
        );
10183
10184
        $arrHide = [$id];
10185
        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...
10186
            if ($action != 'add') {
10187
                if (
10188
                    ($arrLP[$i]['item_type'] == 'dir') &&
10189
                    !in_array($arrLP[$i]['id'], $arrHide) &&
10190
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
10191
                ) {
10192
                    $parentSelect->addOption(
10193
                        $arrLP[$i]['title'],
10194
                        $arrLP[$i]['id'],
10195
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
10196
                    );
10197
10198
                    if ($parent == $arrLP[$i]['id']) {
10199
                        $parentSelect->setSelected($arrLP[$i]['id']);
10200
                    }
10201
                } else {
10202
                    $arrHide[] = $arrLP[$i]['id'];
10203
                }
10204
            } else {
10205
                if ($arrLP[$i]['item_type'] === 'dir') {
10206
                    $parentSelect->addOption(
10207
                        $arrLP[$i]['title'],
10208
                        $arrLP[$i]['id'],
10209
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
10210
                    );
10211
10212
                    if ($parent == $arrLP[$i]['id']) {
10213
                        $parentSelect->setSelected($arrLP[$i]['id']);
10214
                    }
10215
                }
10216
            }
10217
        }
10218
10219
        if (is_array($arrLP)) {
10220
            reset($arrLP);
10221
        }
10222
10223
        $previousSelect = $form->addSelect(
10224
            'previous',
10225
            get_lang('Position'),
10226
            ['0' => get_lang('FirstPosition')],
10227
            ['id' => 'previous', 'class' => 'learnpath_item_form']
10228
        );
10229
10230
        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...
10231
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
10232
                $previousSelect->addOption(
10233
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
10234
                    $arrLP[$i]['id']
10235
                );
10236
10237
                if ($previousId == $arrLP[$i]['id']) {
10238
                    $previousSelect->setSelected($arrLP[$i]['id']);
10239
                } elseif ($action === 'add') {
10240
                    $previousSelect->setSelected($arrLP[$i]['id']);
10241
                }
10242
            }
10243
        }
10244
10245
        if ('edit' === $action) {
10246
            if (true !== api_get_configuration_value('lp_item_prerequisite_dates')) {
10247
                $excludeExtraFields = array_merge($excludeExtraFields, ['start_date', 'end_date']);
10248
            }
10249
            $extraField = new ExtraField('lp_item');
10250
            $extraField->addElements($form, $id, $excludeExtraFields);
10251
        }
10252
10253
        if ($action === 'add') {
10254
            $form->addButtonCreate(get_lang('AddAssignmentToCourse'), 'submit_button');
10255
        } else {
10256
            $form->addButtonCreate(get_lang('EditCurrentStudentPublication'), 'submit_button');
10257
        }
10258
10259
        if ($action === 'move') {
10260
            $form->addHidden('title', $item_title);
10261
            $form->addHidden('description', $item_description);
10262
        }
10263
10264
        if (is_numeric($extra_info)) {
10265
            $form->addHidden('path', $extra_info);
10266
        } elseif (is_array($extra_info)) {
10267
            $form->addHidden('path', $extra_info['path']);
10268
        }
10269
10270
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
10271
        $form->addHidden('post_time', time());
10272
        $this->setAuthorLpItem($form);
10273
        $form->setDefaults(['title' => $item_title, 'start_date' => null]);
10274
10275
        $return = '<div class="sectioncomment">';
10276
        $return .= $form->returnForm();
10277
        $return .= '</div>';
10278
10279
        return $return;
10280
    }
10281
10282
    /**
10283
     * Displays the menu for manipulating a step.
10284
     *
10285
     * @param int    $item_id
10286
     * @param string $item_type
10287
     *
10288
     * @return string
10289
     */
10290
    public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
10291
    {
10292
        $_course = api_get_course_info();
10293
        $course_code = api_get_course_id();
10294
        $item_id = (int) $item_id;
10295
10296
        $return = '<div class="actions">';
10297
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10298
        $sql = "SELECT * FROM $tbl_lp_item
10299
                WHERE iid = ".$item_id;
10300
        $result = Database::query($sql);
10301
        $row = Database::fetch_assoc($result);
10302
10303
        $audio_player = null;
10304
        // We display an audio player if needed.
10305
        if (!empty($row['audio'])) {
10306
            $audio = learnpathItem::fixAudio($row['audio']);
10307
            $webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document'.$audio;
10308
            $audio_player .= '<div class="lp_mediaplayer" id="container">
10309
                            <audio src="'.$webAudioPath.'" controls>
10310
                            </div><br />';
10311
        }
10312
10313
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
10314
10315
        if ($item_type != TOOL_LP_FINAL_ITEM) {
10316
            $return .= Display::url(
10317
                Display::return_icon(
10318
                    'edit.png',
10319
                    get_lang('Edit'),
10320
                    [],
10321
                    ICON_SIZE_SMALL
10322
                ),
10323
                $url.'&action=edit_item&path_item='.$row['path']
10324
            );
10325
10326
            $return .= Display::url(
10327
                Display::return_icon(
10328
                    'move.png',
10329
                    get_lang('Move'),
10330
                    [],
10331
                    ICON_SIZE_SMALL
10332
                ),
10333
                $url.'&action=move_item'
10334
            );
10335
        }
10336
10337
        // Commented for now as prerequisites cannot be added to chapters.
10338
        if ($item_type != 'dir') {
10339
            $return .= Display::url(
10340
                Display::return_icon(
10341
                    'accept.png',
10342
                    get_lang('LearnpathPrerequisites'),
10343
                    [],
10344
                    ICON_SIZE_SMALL
10345
                ),
10346
                $url.'&action=edit_item_prereq'
10347
            );
10348
        }
10349
        $return .= Display::url(
10350
            Display::return_icon(
10351
                'delete.png',
10352
                get_lang('Delete'),
10353
                [],
10354
                ICON_SIZE_SMALL
10355
            ),
10356
            $url.'&action=delete_item'
10357
        );
10358
10359
        if (in_array($item_type, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
10360
            $documentData = DocumentManager::get_document_data_by_id($row['path'], $course_code);
10361
            if (empty($documentData)) {
10362
                // Try with iid
10363
                $table = Database::get_course_table(TABLE_DOCUMENT);
10364
                $sql = "SELECT path FROM $table
10365
                        WHERE
10366
                              c_id = ".api_get_course_int_id()." AND
10367
                              iid = ".$row['path']." AND
10368
                              path NOT LIKE '%_DELETED_%'";
10369
                $result = Database::query($sql);
10370
                $documentData = Database::fetch_array($result);
10371
                if ($documentData) {
10372
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
10373
                }
10374
            }
10375
            if (isset($documentData['absolute_path_from_document'])) {
10376
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
10377
            }
10378
        }
10379
10380
        $return .= '</div>';
10381
10382
        if (!empty($audio_player)) {
10383
            $return .= $audio_player;
10384
        }
10385
10386
        return $return;
10387
    }
10388
10389
    /**
10390
     * Creates the javascript needed for filling up the checkboxes without page reload.
10391
     *
10392
     * @return string
10393
     */
10394
    public function get_js_dropdown_array()
10395
    {
10396
        $course_id = api_get_course_int_id();
10397
        $return = 'var child_name = new Array();'."\n";
10398
        $return .= 'var child_value = new Array();'."\n\n";
10399
        $return .= 'child_name[0] = new Array();'."\n";
10400
        $return .= 'child_value[0] = new Array();'."\n\n";
10401
10402
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10403
        $i = 0;
10404
        $list = $this->getItemsForForm(true);
10405
10406
        foreach ($list as $row_zero) {
10407
            if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
10408
                if ($row_zero['item_type'] == TOOL_QUIZ) {
10409
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
10410
                }
10411
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
10412
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
10413
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
10414
            }
10415
        }
10416
10417
        $return .= "\n";
10418
        $sql = "SELECT * FROM $tbl_lp_item
10419
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
10420
        $res = Database::query($sql);
10421
        while ($row = Database::fetch_array($res)) {
10422
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
10423
                           WHERE
10424
                                c_id = ".$course_id." AND
10425
                                parent_item_id = ".$row['iid']."
10426
                           ORDER BY display_order ASC";
10427
            $res_parent = Database::query($sql_parent);
10428
            $i = 0;
10429
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
10430
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
10431
10432
            while ($row_parent = Database::fetch_array($res_parent)) {
10433
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
10434
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
10435
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
10436
            }
10437
            $return .= "\n";
10438
        }
10439
10440
        $return .= "
10441
            function load_cbo(id) {
10442
                if (!id) {
10443
                    return false;
10444
                }
10445
10446
                var cbo = document.getElementById('previous');
10447
                for (var i = cbo.length - 1; i > 0; i--) {
10448
                    cbo.options[i] = null;
10449
                }
10450
10451
                var k=0;
10452
                for(var i = 1; i <= child_name[id].length; i++){
10453
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
10454
                    option.style.paddingLeft = '40px';
10455
                    cbo.options[i] = option;
10456
                    k = i;
10457
                }
10458
10459
                cbo.options[k].selected = true;
10460
                $('#previous').selectpicker('refresh');
10461
            }";
10462
10463
        return $return;
10464
    }
10465
10466
    /**
10467
     * Display the form to allow moving an item.
10468
     *
10469
     * @param learnpathItem $item Item ID
10470
     *
10471
     * @throws Exception
10472
     * @throws HTML_QuickForm_Error
10473
     *
10474
     * @return string HTML form
10475
     */
10476
    public function display_move_item($item)
10477
    {
10478
        $return = '';
10479
        if ($item) {
10480
            $item_id = $item->getIid();
10481
            $type = $item->get_type();
10482
            $path = (int) $item->get_path();
10483
10484
            switch ($type) {
10485
                case 'dir':
10486
                case 'asset':
10487
                    $return .= $this->display_manipulate($item_id, $type);
10488
                    $return .= $this->display_item_form(
10489
                        $type,
10490
                        get_lang('MoveCurrentChapter'),
10491
                        'move',
10492
                        $item_id,
10493
                        $item
10494
                    );
10495
                    break;
10496
                case TOOL_DOCUMENT:
10497
                    $return .= $this->display_manipulate($item_id, $type);
10498
                    $return .= $this->display_document_form('move', $item_id, null, $item);
10499
                    break;
10500
                case TOOL_LINK:
10501
                    $return .= $this->display_manipulate($item_id, $type);
10502
                    $return .= $this->display_link_form('move', $item_id, $path, $item);
10503
                    break;
10504
                case TOOL_HOTPOTATOES:
10505
                    $return .= $this->display_manipulate($item_id, $type);
10506
                    $return .= $this->display_link_form('move', $item_id, $item);
10507
                    break;
10508
                case TOOL_QUIZ:
10509
                    $return .= $this->display_manipulate($item_id, $type);
10510
                    $return .= $this->display_quiz_form('move', $item_id, $item);
10511
                    break;
10512
                case TOOL_STUDENTPUBLICATION:
10513
                    $return .= $this->display_manipulate($item_id, $type);
10514
                    $return .= $this->display_student_publication_form('move', $item_id, $path, $item);
10515
                    break;
10516
                case TOOL_FORUM:
10517
                    $return .= $this->display_manipulate($item_id, $type);
10518
                    $return .= $this->display_forum_form('move', $item_id, $path);
10519
                    break;
10520
                case TOOL_THREAD:
10521
                    $return .= $this->display_manipulate($item_id, $type);
10522
                    $return .= $this->display_forum_form('move', $item_id, $path);
10523
                    break;
10524
            }
10525
        }
10526
10527
        return $return;
10528
    }
10529
10530
    /**
10531
     * Return HTML form to allow prerequisites selection.
10532
     *
10533
     * @todo use FormValidator
10534
     *
10535
     * @param int Item ID
10536
     *
10537
     * @return string HTML form
10538
     */
10539
    public function display_item_prerequisites_form($item_id = 0)
10540
    {
10541
        $course_id = api_get_course_int_id();
10542
        $item_id = (int) $item_id;
10543
10544
        if (empty($item_id)) {
10545
            return '';
10546
        }
10547
10548
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10549
10550
        /* Current prerequisite */
10551
        $sql = "SELECT * FROM $tbl_lp_item
10552
                WHERE iid = $item_id";
10553
        $result = Database::query($sql);
10554
        $row = Database::fetch_array($result);
10555
        $prerequisiteId = $row['prerequisite'];
10556
10557
        $return = '<legend>';
10558
        $return .= get_lang('AddEditPrerequisites');
10559
        $return .= '</legend>';
10560
        $return .= '<form method="POST">';
10561
        $return .= '<div class="table-responsive">';
10562
        $return .= '<table class="table table-hover">';
10563
        $return .= '<thead>';
10564
        $return .= '<tr>';
10565
        $return .= '<th>'.get_lang('LearnpathPrerequisites').'</th>';
10566
        $return .= '<th width="140">'.get_lang('Minimum').'</th>';
10567
        $return .= '<th width="140">'.get_lang('Maximum').'</th>';
10568
        $return .= '</tr>';
10569
        $return .= '</thead>';
10570
10571
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
10572
        $return .= '<tbody>';
10573
        $return .= '<tr>';
10574
        $return .= '<td colspan="3">';
10575
        $return .= '<div class="radio learnpath"><label for="idNone">';
10576
        $return .= '<input checked="checked" id="idNone" name="prerequisites" type="radio" />';
10577
        $return .= get_lang('None').'</label>';
10578
        $return .= '</div>';
10579
        $return .= '</tr>';
10580
10581
        $sql = "SELECT * FROM $tbl_lp_item
10582
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
10583
        $result = Database::query($sql);
10584
10585
        $selectedMinScore = [];
10586
        $selectedMaxScore = [];
10587
        $masteryScore = [];
10588
        while ($row = Database::fetch_array($result)) {
10589
            if ($row['iid'] == $item_id) {
10590
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
10591
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
10592
            }
10593
            $masteryScore[$row['iid']] = $row['mastery_score'];
10594
        }
10595
10596
        $arrLP = $this->getItemsForForm();
10597
        $this->tree_array($arrLP);
10598
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
10599
        unset($this->arrMenu);
10600
10601
        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...
10602
            $item = $arrLP[$i];
10603
10604
            if ($item['id'] == $item_id) {
10605
                break;
10606
            }
10607
10608
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
10609
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
10610
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
10611
10612
            $return .= '<tr>';
10613
            $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
10614
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
10615
            $return .= '<label for="id'.$item['id'].'">';
10616
10617
            $checked = '';
10618
            if (null !== $prerequisiteId) {
10619
                $checked = in_array($prerequisiteId, [$item['id'], $item['ref']]) ? ' checked="checked" ' : '';
10620
            }
10621
10622
            $disabled = $item['item_type'] === 'dir' ? ' disabled="disabled" ' : '';
10623
10624
            $return .= '<input '.$checked.' '.$disabled.' id="id'.$item['id'].'" name="prerequisites" type="radio" value="'.$item['id'].'" />';
10625
10626
            $icon_name = str_replace(' ', '', $item['item_type']);
10627
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
10628
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
10629
            } else {
10630
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
10631
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
10632
                } else {
10633
                    $return .= Display::return_icon('folder_document.png');
10634
                }
10635
            }
10636
10637
            $return .= $item['title'].'</label>';
10638
            $return .= '</div>';
10639
            $return .= '</td>';
10640
10641
            if ($item['item_type'] == TOOL_QUIZ) {
10642
                // lets update max_score Quiz information depending of the Quiz Advanced properties
10643
                $lpItemObj = new LpItem($course_id, $item['id']);
10644
                $exercise = new Exercise($course_id);
10645
                $exercise->read($lpItemObj->path);
10646
                $lpItemObj->max_score = $exercise->get_max_score();
10647
                $lpItemObj->update();
10648
                $item['max_score'] = $lpItemObj->max_score;
10649
10650
                //if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
10651
                if (!isset($selectedMinScore[$item['id']]) && !empty($masteryScoreAsMinValue)) {
10652
                    // Backwards compatibility with 1.9.x use mastery_score as min value
10653
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
10654
                }
10655
10656
                $return .= '<td>';
10657
                $return .= '<input
10658
                    class="form-control"
10659
                    size="4" maxlength="3"
10660
                    name="min_'.$item['id'].'"
10661
                    type="number"
10662
                    min="0"
10663
                    step="any"
10664
                    max="'.$item['max_score'].'"
10665
                    value="'.$selectedMinScoreValue.'"
10666
                />';
10667
                $return .= '</td>';
10668
                $return .= '<td>';
10669
                $return .= '<input
10670
                    class="form-control"
10671
                    size="4"
10672
                    maxlength="3"
10673
                    name="max_'.$item['id'].'"
10674
                    type="number"
10675
                    min="0"
10676
                    step="any"
10677
                    max="'.$item['max_score'].'"
10678
                    value="'.$selectedMaxScoreValue.'"
10679
                />';
10680
                $return .= '</td>';
10681
            }
10682
10683
            if ($item['item_type'] == TOOL_HOTPOTATOES) {
10684
                $return .= '<td>';
10685
                $return .= '<input
10686
                    size="4"
10687
                    maxlength="3"
10688
                    name="min_'.$item['id'].'"
10689
                    type="number"
10690
                    min="0"
10691
                    step="any"
10692
                    max="'.$item['max_score'].'"
10693
                    value="'.$selectedMinScoreValue.'"
10694
                />';
10695
                $return .= '</td>';
10696
                $return .= '<td>';
10697
                $return .= '<input
10698
                    size="4"
10699
                    maxlength="3"
10700
                    name="max_'.$item['id'].'"
10701
                    type="number"
10702
                    min="0"
10703
                    step="any"
10704
                    max="'.$item['max_score'].'"
10705
                    value="'.$selectedMaxScoreValue.'"
10706
                />';
10707
                $return .= '</td>';
10708
            }
10709
            $return .= '</tr>';
10710
        }
10711
        $return .= '<tr>';
10712
        $return .= '</tr>';
10713
        $return .= '</tbody>';
10714
        $return .= '</table>';
10715
        $return .= '</div>';
10716
        $return .= '<div class="form-group">';
10717
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
10718
            get_lang('ModifyPrerequisites').'</button>';
10719
        $return .= '</form>';
10720
10721
        return $return;
10722
    }
10723
10724
    /**
10725
     * Return HTML list to allow prerequisites selection for lp.
10726
     *
10727
     * @return string HTML form
10728
     */
10729
    public function display_lp_prerequisites_list()
10730
    {
10731
        $course_id = api_get_course_int_id();
10732
        $lp_id = $this->lp_id;
10733
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
10734
10735
        // get current prerequisite
10736
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
10737
        $result = Database::query($sql);
10738
        $row = Database::fetch_array($result);
10739
        $prerequisiteId = $row['prerequisite'];
10740
        $session_id = api_get_session_id();
10741
        $session_condition = api_get_session_condition($session_id, true, true);
10742
        $sql = "SELECT * FROM $tbl_lp
10743
                WHERE c_id = $course_id $session_condition
10744
                ORDER BY display_order ";
10745
        $rs = Database::query($sql);
10746
        $return = '';
10747
        $return .= '<select name="prerequisites" class="form-control">';
10748
        $return .= '<option value="0">'.get_lang('None').'</option>';
10749
        if (Database::num_rows($rs) > 0) {
10750
            while ($row = Database::fetch_array($rs)) {
10751
                if ($row['id'] == $lp_id) {
10752
                    continue;
10753
                }
10754
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
10755
            }
10756
        }
10757
        $return .= '</select>';
10758
10759
        return $return;
10760
    }
10761
10762
    /**
10763
     * Creates a list with all the documents in it.
10764
     *
10765
     * @param bool $showInvisibleFiles
10766
     *
10767
     * @throws Exception
10768
     * @throws HTML_QuickForm_Error
10769
     *
10770
     * @return string
10771
     */
10772
    public function get_documents($showInvisibleFiles = false)
10773
    {
10774
        $course_info = api_get_course_info();
10775
        $sessionId = api_get_session_id();
10776
        $documentTree = DocumentManager::get_document_preview(
10777
            $course_info,
10778
            $this->lp_id,
10779
            null,
10780
            $sessionId,
10781
            true,
10782
            null,
10783
            null,
10784
            $showInvisibleFiles,
10785
            true
10786
        );
10787
10788
        $headers = [
10789
            get_lang('Files'),
10790
            get_lang('CreateTheDocument'),
10791
            get_lang('CreateReadOutText'),
10792
            get_lang('Upload'),
10793
        ];
10794
10795
        $form = new FormValidator(
10796
            'form_upload',
10797
            'POST',
10798
            $this->getCurrentBuildingModeURL(),
10799
            '',
10800
            ['enctype' => 'multipart/form-data']
10801
        );
10802
10803
        $folders = DocumentManager::get_all_document_folders(
10804
            api_get_course_info(),
10805
            0,
10806
            true
10807
        );
10808
10809
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
10810
10811
        DocumentManager::build_directory_selector(
10812
            $folders,
10813
            $lpPathInfo['id'],
10814
            [],
10815
            true,
10816
            $form,
10817
            'directory_parent_id'
10818
        );
10819
10820
        $group = [
10821
            $form->createElement(
10822
                'radio',
10823
                'if_exists',
10824
                get_lang('UplWhatIfFileExists'),
10825
                get_lang('UplDoNothing'),
10826
                'nothing'
10827
            ),
10828
            $form->createElement(
10829
                'radio',
10830
                'if_exists',
10831
                null,
10832
                get_lang('UplOverwriteLong'),
10833
                'overwrite'
10834
            ),
10835
            $form->createElement(
10836
                'radio',
10837
                'if_exists',
10838
                null,
10839
                get_lang('UplRenameLong'),
10840
                'rename'
10841
            ),
10842
        ];
10843
        $form->addGroup($group, null, get_lang('UplWhatIfFileExists'));
10844
10845
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
10846
        $defaultFileExistsOption = 'rename';
10847
        if (!empty($fileExistsOption)) {
10848
            $defaultFileExistsOption = $fileExistsOption;
10849
        }
10850
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
10851
10852
        // Check box options
10853
        $form->addElement(
10854
            'checkbox',
10855
            'unzip',
10856
            get_lang('Options'),
10857
            get_lang('Uncompress')
10858
        );
10859
10860
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
10861
        $form->addMultipleUpload($url);
10862
        $new = $this->display_document_form('add', 0);
10863
        $frmReadOutText = $this->displayFrmReadOutText('add');
10864
        $tabs = Display::tabs(
10865
            $headers,
10866
            [$documentTree, $new, $frmReadOutText, $form->returnForm()],
10867
            'subtab'
10868
        );
10869
10870
        return $tabs;
10871
    }
10872
10873
    /**
10874
     * Creates a list with all the exercises (quiz) in it.
10875
     *
10876
     * @return string
10877
     */
10878
    public function get_exercises()
10879
    {
10880
        $course_id = api_get_course_int_id();
10881
        $session_id = api_get_session_id();
10882
        $userInfo = api_get_user_info();
10883
10884
        // New for hotpotatoes.
10885
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
10886
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
10887
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
10888
        $condition_session = api_get_session_condition($session_id, true, true);
10889
        $setting = api_get_configuration_value('show_invisible_exercise_in_lp_list');
10890
10891
        $activeCondition = ' active <> -1 ';
10892
        if ($setting) {
10893
            $activeCondition = ' active = 1 ';
10894
        }
10895
10896
        $categoryCondition = '';
10897
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
10898
        if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
10899
            $categoryCondition = " AND exercise_category_id = $categoryId ";
10900
        }
10901
10902
        $keywordCondition = '';
10903
        $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
10904
10905
        if (!empty($keyword)) {
10906
            $keyword = Database::escape_string($keyword);
10907
            $keywordCondition = " AND title LIKE '%$keyword%' ";
10908
        }
10909
10910
        $sql_quiz = "SELECT * FROM $tbl_quiz
10911
                     WHERE
10912
                            c_id = $course_id AND
10913
                            $activeCondition
10914
                            $condition_session
10915
                            $categoryCondition
10916
                            $keywordCondition
10917
                     ORDER BY title ASC";
10918
10919
        $sql_hot = "SELECT * FROM $tbl_doc
10920
                    WHERE
10921
                        c_id = $course_id AND
10922
                        path LIKE '".$uploadPath."/%/%htm%'
10923
                        $condition_session
10924
                     ORDER BY id ASC";
10925
10926
        $res_quiz = Database::query($sql_quiz);
10927
        $res_hot = Database::query($sql_hot);
10928
10929
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
10930
10931
        // Create a search-box
10932
        $form = new FormValidator('search_simple', 'get', $currentUrl);
10933
        $form->addHidden('action', 'add_item');
10934
        $form->addHidden('type', 'step');
10935
        $form->addHidden('lp_id', $this->lp_id);
10936
        $form->addHidden('lp_build_selected', '2');
10937
10938
        $form->addCourseHiddenParams();
10939
        $form->addText(
10940
            'keyword',
10941
            get_lang('Search'),
10942
            false,
10943
            [
10944
                'aria-label' => get_lang('Search'),
10945
            ]
10946
        );
10947
10948
        if (api_get_configuration_value('allow_exercise_categories')) {
10949
            $manager = new ExerciseCategoryManager();
10950
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
10951
            if (!empty($options)) {
10952
                $form->addSelect(
10953
                    'category_id',
10954
                    get_lang('Category'),
10955
                    $options,
10956
                    ['placeholder' => get_lang('SelectAnOption')]
10957
                );
10958
            }
10959
        }
10960
10961
        $form->addButtonSearch(get_lang('Search'));
10962
        $return = $form->returnForm();
10963
10964
        $return .= '<ul class="lp_resource">';
10965
10966
        $return .= '<li class="lp_resource_element">';
10967
        $return .= Display::return_icon('new_exercice.png');
10968
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
10969
            get_lang('NewExercise').'</a>';
10970
        $return .= '</li>';
10971
10972
        $previewIcon = Display::return_icon(
10973
            'preview_view.png',
10974
            get_lang('Preview')
10975
        );
10976
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
10977
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10978
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/showinframes.php?'.api_get_cidreq();
10979
10980
        // Display hotpotatoes
10981
        while ($row_hot = Database::fetch_array($res_hot)) {
10982
            $link = Display::url(
10983
                $previewIcon,
10984
                $exerciseUrl.'&file='.$row_hot['path'],
10985
                ['target' => '_blank']
10986
            );
10987
            $return .= '<li class="lp_resource_element" data_id="'.$row_hot['id'].'" data_type="hotpotatoes" title="'.$row_hot['title'].'" >';
10988
            $return .= '<a class="moved" href="#">';
10989
            $return .= Display::return_icon(
10990
                'move_everywhere.png',
10991
                get_lang('Move'),
10992
                [],
10993
                ICON_SIZE_TINY
10994
            );
10995
            $return .= '</a> ';
10996
            $return .= Display::return_icon('hotpotatoes_s.png');
10997
            $return .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_HOTPOTATOES.'&file='.$row_hot['id'].'&lp_id='.$this->lp_id.'">'.
10998
                ((!empty($row_hot['comment'])) ? $row_hot['comment'] : Security::remove_XSS($row_hot['title'])).$link.'</a>';
10999
            $return .= '</li>';
11000
        }
11001
11002
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
11003
        while ($row_quiz = Database::fetch_array($res_quiz)) {
11004
            $title = strip_tags(
11005
                api_html_entity_decode($row_quiz['title'])
11006
            );
11007
11008
            $visibility = api_get_item_visibility(
11009
                ['real_id' => $course_id],
11010
                TOOL_QUIZ,
11011
                $row_quiz['iid'],
11012
                $session_id
11013
            );
11014
11015
            $link = Display::url(
11016
                $previewIcon,
11017
                $exerciseUrl.'&exerciseId='.$row_quiz['iid'],
11018
                ['target' => '_blank']
11019
            );
11020
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['iid'].'" data_type="quiz" title="'.$title.'" >';
11021
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
11022
            $return .= $quizIcon;
11023
            $sessionStar = api_get_session_image(
11024
                $row_quiz['session_id'],
11025
                $userInfo['status']
11026
            );
11027
            $return .= Display::url(
11028
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
11029
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['iid'].'&lp_id='.$this->lp_id,
11030
                [
11031
                    'class' => $visibility == 0 ? 'moved text-muted' : 'moved',
11032
                ]
11033
            );
11034
            $return .= '</li>';
11035
        }
11036
11037
        $return .= '</ul>';
11038
11039
        return $return;
11040
    }
11041
11042
    /**
11043
     * Creates a list with all the links in it.
11044
     *
11045
     * @return string
11046
     */
11047
    public function get_links()
11048
    {
11049
        $selfUrl = api_get_self();
11050
        $courseIdReq = api_get_cidreq();
11051
        $course = api_get_course_info();
11052
        $userInfo = api_get_user_info();
11053
11054
        $course_id = $course['real_id'];
11055
        $tbl_link = Database::get_course_table(TABLE_LINK);
11056
        $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
11057
        $moveEverywhereIcon = Display::return_icon(
11058
            'move_everywhere.png',
11059
            get_lang('Move'),
11060
            [],
11061
            ICON_SIZE_TINY
11062
        );
11063
11064
        $session_id = api_get_session_id();
11065
        $condition_session = api_get_session_condition(
11066
            $session_id,
11067
            true,
11068
            true,
11069
            'link.session_id'
11070
        );
11071
11072
        $sql = "SELECT
11073
                    link.id as link_id,
11074
                    link.title as link_title,
11075
                    link.session_id as link_session_id,
11076
                    link.category_id as category_id,
11077
                    link_category.category_title as category_title
11078
                FROM $tbl_link as link
11079
                LEFT JOIN $linkCategoryTable as link_category
11080
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
11081
                WHERE link.c_id = $course_id $condition_session
11082
                ORDER BY link_category.category_title ASC, link.title ASC";
11083
        $result = Database::query($sql);
11084
        $categorizedLinks = [];
11085
        $categories = [];
11086
11087
        while ($link = Database::fetch_array($result)) {
11088
            if (!$link['category_id']) {
11089
                $link['category_title'] = get_lang('Uncategorized');
11090
            }
11091
            $categories[$link['category_id']] = $link['category_title'];
11092
            $categorizedLinks[$link['category_id']][$link['link_id']] = $link;
11093
        }
11094
11095
        $linksHtmlCode =
11096
            '<script>
11097
            function toggle_tool(tool, id) {
11098
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
11099
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
11100
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
11101
                } else {
11102
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
11103
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
11104
                }
11105
            }
11106
        </script>
11107
11108
        <ul class="lp_resource">
11109
            <li class="lp_resource_element">
11110
                '.Display::return_icon('linksnew.gif').'
11111
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('LinkAdd').'">'.
11112
                get_lang('LinkAdd').'
11113
                </a>
11114
            </li>';
11115
11116
        foreach ($categorizedLinks as $categoryId => $links) {
11117
            $linkNodes = null;
11118
            foreach ($links as $key => $linkInfo) {
11119
                $title = $linkInfo['link_title'];
11120
                $linkSessionId = $linkInfo['link_session_id'];
11121
11122
                $link = Display::url(
11123
                    Display::return_icon('preview_view.png', get_lang('Preview')),
11124
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
11125
                    ['target' => '_blank']
11126
                );
11127
11128
                if (api_get_item_visibility($course, TOOL_LINK, $key, $session_id) != 2) {
11129
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
11130
                    $linkNodes .=
11131
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
11132
                        <a class="moved" href="#">'.
11133
                            $moveEverywhereIcon.
11134
                        '</a>
11135
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
11136
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
11137
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
11138
                        Security::remove_XSS($title).$sessionStar.$link.
11139
                        '</a>
11140
                    </li>';
11141
                }
11142
            }
11143
            $linksHtmlCode .=
11144
                '<li>
11145
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
11146
                    <img src="'.Display::returnIconPath('add.gif').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
11147
                    align="absbottom" />
11148
                </a>
11149
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
11150
            </li>
11151
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
11152
        }
11153
        $linksHtmlCode .= '</ul>';
11154
11155
        return $linksHtmlCode;
11156
    }
11157
11158
    /**
11159
     * Creates a list with all the surveys in it.
11160
     *
11161
     * @return string
11162
     */
11163
    public function getSurveys()
11164
    {
11165
        $return = '<ul class="lp_resource">';
11166
        // First add link
11167
        $return .= '<li class="lp_resource_element">';
11168
        $return .= Display::return_icon('new_survey.png', get_lang('CreateNewSurvey'), '', ICON_SIZE_MEDIUM);
11169
        $return .= Display::url(
11170
            get_lang('CreateANewSurvey'),
11171
            api_get_path(WEB_CODE_PATH).'survey/create_new_survey.php?'.api_get_cidreq().'&'.http_build_query([
11172
                'action' => 'add',
11173
                'lp_id' => $this->lp_id,
11174
            ]),
11175
            ['title' => get_lang('CreateNewSurvey')]
11176
        );
11177
        $return .= '</li>';
11178
11179
        $surveys = SurveyManager::get_surveys(api_get_course_id(), api_get_session_id());
11180
11181
        foreach ($surveys as $survey) {
11182
            if (!empty($survey['survey_id'])) {
11183
                $surveyTitle = strip_tags($survey['title']);
11184
                $return .= '<li class="lp_resource_element" data_id="'.$survey['survey_id'].'" data_type="'.TOOL_SURVEY.'" title="'.$surveyTitle.'" >';
11185
                $return .= '<a class="moved" href="#">';
11186
                $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
11187
                $return .= ' </a>';
11188
                $return .= Display::return_icon('survey.png', '', [], ICON_SIZE_TINY);
11189
                $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>';
11190
                $return .= '</li>';
11191
            }
11192
        }
11193
11194
        $return .= '</ul>';
11195
11196
        return $return;
11197
    }
11198
11199
    /**
11200
     * Creates a list with all the student publications in it.
11201
     *
11202
     * @return string
11203
     */
11204
    public function get_student_publications()
11205
    {
11206
        $return = '<ul class="lp_resource">';
11207
        $return .= '<li class="lp_resource_element">';
11208
        $return .= Display::return_icon('works_new.gif');
11209
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
11210
            get_lang('AddAssignmentPage').'</a>';
11211
        $return .= '</li>';
11212
11213
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
11214
        $works = getWorkListTeacher(0, 100, null, null, null);
11215
        if (!empty($works)) {
11216
            foreach ($works as $work) {
11217
                $link = Display::url(
11218
                    Display::return_icon('preview_view.png', get_lang('Preview')),
11219
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
11220
                    ['target' => '_blank']
11221
                );
11222
11223
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
11224
                $return .= '<a class="moved" href="#">';
11225
                $return .= Display::return_icon(
11226
                    'move_everywhere.png',
11227
                    get_lang('Move'),
11228
                    [],
11229
                    ICON_SIZE_TINY
11230
                );
11231
                $return .= '</a> ';
11232
11233
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
11234
                $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.'">'.
11235
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
11236
                </a>';
11237
11238
                $return .= '</li>';
11239
            }
11240
        }
11241
11242
        $return .= '</ul>';
11243
11244
        return $return;
11245
    }
11246
11247
    /**
11248
     * Creates a list with all the forums in it.
11249
     *
11250
     * @return string
11251
     */
11252
    public function get_forums()
11253
    {
11254
        require_once '../forum/forumfunction.inc.php';
11255
11256
        $forumCategories = get_forum_categories();
11257
        $forumsInNoCategory = get_forums_in_category(0);
11258
        if (!empty($forumsInNoCategory)) {
11259
            $forumCategories = array_merge(
11260
                $forumCategories,
11261
                [
11262
                    [
11263
                        'cat_id' => 0,
11264
                        'session_id' => 0,
11265
                        'visibility' => 1,
11266
                        'cat_comment' => null,
11267
                    ],
11268
                ]
11269
            );
11270
        }
11271
11272
        $forumList = get_forums();
11273
        $a_forums = [];
11274
        foreach ($forumCategories as $forumCategory) {
11275
            // The forums in this category.
11276
            $forumsInCategory = get_forums_in_category($forumCategory['cat_id']);
11277
            if (!empty($forumsInCategory)) {
11278
                foreach ($forumList as $forum) {
11279
                    if (isset($forum['forum_category']) &&
11280
                        $forum['forum_category'] == $forumCategory['cat_id']
11281
                    ) {
11282
                        $a_forums[] = $forum;
11283
                    }
11284
                }
11285
            }
11286
        }
11287
11288
        $return = '<ul class="lp_resource">';
11289
11290
        // First add link
11291
        $return .= '<li class="lp_resource_element">';
11292
        $return .= Display::return_icon('new_forum.png');
11293
        $return .= Display::url(
11294
            get_lang('CreateANewForum'),
11295
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
11296
                'action' => 'add',
11297
                'content' => 'forum',
11298
                'lp_id' => $this->lp_id,
11299
            ]),
11300
            ['title' => get_lang('CreateANewForum')]
11301
        );
11302
        $return .= '</li>';
11303
11304
        $return .= '<script>
11305
            function toggle_forum(forum_id) {
11306
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
11307
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
11308
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
11309
                } else {
11310
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
11311
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
11312
                }
11313
            }
11314
        </script>';
11315
11316
        foreach ($a_forums as $forum) {
11317
            if (!empty($forum['forum_id'])) {
11318
                $link = Display::url(
11319
                    Display::return_icon('preview_view.png', get_lang('Preview')),
11320
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forum['forum_id'],
11321
                    ['target' => '_blank']
11322
                );
11323
11324
                $return .= '<li class="lp_resource_element" data_id="'.$forum['forum_id'].'" data_type="'.TOOL_FORUM.'" title="'.$forum['forum_title'].'" >';
11325
                $return .= '<a class="moved" href="#">';
11326
                $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
11327
                $return .= ' </a>';
11328
                $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
11329
                $return .= '<a onclick="javascript:toggle_forum('.$forum['forum_id'].');" style="cursor:hand; vertical-align:middle">
11330
                                <img src="'.Display::returnIconPath('add.gif').'" id="forum_'.$forum['forum_id'].'_opener" align="absbottom" />
11331
                            </a>
11332
                            <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">'.
11333
                    Security::remove_XSS($forum['forum_title']).' '.$link.'</a>';
11334
11335
                $return .= '</li>';
11336
11337
                $return .= '<div style="display:none" id="forum_'.$forum['forum_id'].'_content">';
11338
                $a_threads = get_threads($forum['forum_id']);
11339
                if (is_array($a_threads)) {
11340
                    foreach ($a_threads as $thread) {
11341
                        $link = Display::url(
11342
                            Display::return_icon('preview_view.png', get_lang('Preview')),
11343
                            api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forum['forum_id'].'&thread='.$thread['thread_id'],
11344
                            ['target' => '_blank']
11345
                        );
11346
11347
                        $return .= '<li class="lp_resource_element" data_id="'.$thread['thread_id'].'" data_type="'.TOOL_THREAD.'" title="'.$thread['thread_title'].'" >';
11348
                        $return .= '&nbsp;<a class="moved" href="#">';
11349
                        $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
11350
                        $return .= ' </a>';
11351
                        $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
11352
                        $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.'">'.
11353
                            Security::remove_XSS($thread['thread_title']).' '.$link.'</a>';
11354
                        $return .= '</li>';
11355
                    }
11356
                }
11357
                $return .= '</div>';
11358
            }
11359
        }
11360
        $return .= '</ul>';
11361
11362
        return $return;
11363
    }
11364
11365
    /**
11366
     * // TODO: The output encoding should be equal to the system encoding.
11367
     *
11368
     * Exports the learning path as a SCORM package. This is the main function that
11369
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
11370
     * whole thing and returns the zip.
11371
     *
11372
     * This method needs to be called in PHP5, as it will fail with non-adequate
11373
     * XML package (like the ones for PHP4), and it is *not* a static method, so
11374
     * you need to call it on a learnpath object.
11375
     *
11376
     * @TODO The method might be redefined later on in the scorm class itself to avoid
11377
     * creating a SCORM structure if there is one already. However, if the initial SCORM
11378
     * path has been modified, it should use the generic method here below.
11379
     *
11380
     * @return string Returns the zip package string, or null if error
11381
     */
11382
    public function scormExport()
11383
    {
11384
        api_set_more_memory_and_time_limits();
11385
11386
        $_course = api_get_course_info();
11387
        $course_id = $_course['real_id'];
11388
        // Create the zip handler (this will remain available throughout the method).
11389
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
11390
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
11391
        $temp_dir_short = uniqid('scorm_export', true);
11392
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
11393
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
11394
        $zip_folder = new PclZip($temp_zip_file);
11395
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
11396
        $root_path = $main_path = api_get_path(SYS_PATH);
11397
        $files_cleanup = [];
11398
11399
        // Place to temporarily stash the zip file.
11400
        // create the temp dir if it doesn't exist
11401
        // or do a cleanup before creating the zip file.
11402
        if (!is_dir($temp_zip_dir)) {
11403
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
11404
        } else {
11405
            // Cleanup: Check the temp dir for old files and delete them.
11406
            $handle = opendir($temp_zip_dir);
11407
            while (false !== ($file = readdir($handle))) {
11408
                if ($file != '.' && $file != '..') {
11409
                    unlink("$temp_zip_dir/$file");
11410
                }
11411
            }
11412
            closedir($handle);
11413
        }
11414
        $zip_files = $zip_files_abs = $zip_files_dist = [];
11415
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
11416
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
11417
        ) {
11418
            // Remove the possible . at the end of the path.
11419
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
11420
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
11421
            mkdir(
11422
                $dest_path_to_scorm_folder,
11423
                api_get_permissions_for_new_directories(),
11424
                true
11425
            );
11426
            copyr(
11427
                $current_course_path.'/scorm/'.$this->path,
11428
                $dest_path_to_scorm_folder,
11429
                ['imsmanifest'],
11430
                $zip_files
11431
            );
11432
        }
11433
11434
        // Build a dummy imsmanifest structure.
11435
        // Do not add to the zip yet (we still need it).
11436
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
11437
        // Aggregation Model official document, section "2.3 Content Packaging".
11438
        // We are going to build a UTF-8 encoded manifest.
11439
        // Later we will recode it to the desired (and supported) encoding.
11440
        $xmldoc = new DOMDocument('1.0');
11441
        $root = $xmldoc->createElement('manifest');
11442
        $root->setAttribute('identifier', 'SingleCourseManifest');
11443
        $root->setAttribute('version', '1.1');
11444
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
11445
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
11446
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
11447
        $root->setAttribute(
11448
            'xsi:schemaLocation',
11449
            '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'
11450
        );
11451
        // Build mandatory sub-root container elements.
11452
        $metadata = $xmldoc->createElement('metadata');
11453
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
11454
        $metadata->appendChild($md_schema);
11455
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
11456
        $metadata->appendChild($md_schemaversion);
11457
        $root->appendChild($metadata);
11458
11459
        $organizations = $xmldoc->createElement('organizations');
11460
        $resources = $xmldoc->createElement('resources');
11461
11462
        // Build the only organization we will use in building our learnpaths.
11463
        $organizations->setAttribute('default', 'chamilo_scorm_export');
11464
        $organization = $xmldoc->createElement('organization');
11465
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
11466
        // To set the title of the SCORM entity (=organization), we take the name given
11467
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
11468
        // learning path charset) as it is the encoding that defines how it is stored
11469
        // in the database. Then we convert it to HTML entities again as the "&" character
11470
        // alone is not authorized in XML (must be &amp;).
11471
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
11472
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
11473
        $organization->appendChild($org_title);
11474
        $folder_name = 'document';
11475
11476
        // Removes the learning_path/scorm_folder path when exporting see #4841
11477
        $path_to_remove = '';
11478
        $path_to_replace = '';
11479
        $result = $this->generate_lp_folder($_course);
11480
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
11481
            $path_to_remove = 'document'.$result['dir'];
11482
            $path_to_replace = $folder_name.'/';
11483
        }
11484
11485
        // Fixes chamilo scorm exports
11486
        if ($this->ref === 'chamilo_scorm_export') {
11487
            $path_to_remove = 'scorm/'.$this->path.'/document/';
11488
        }
11489
11490
        // For each element, add it to the imsmanifest structure, then add it to the zip.
11491
        $link_updates = [];
11492
        $links_to_create = [];
11493
        foreach ($this->ordered_items as $index => $itemId) {
11494
            /** @var learnpathItem $item */
11495
            $item = $this->items[$itemId];
11496
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
11497
                // Get included documents from this item.
11498
                if ($item->type === 'sco') {
11499
                    $inc_docs = $item->get_resources_from_source(
11500
                        null,
11501
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
11502
                    );
11503
                } else {
11504
                    $inc_docs = $item->get_resources_from_source();
11505
                }
11506
11507
                // Give a child element <item> to the <organization> element.
11508
                $my_item_id = $item->get_id();
11509
                $my_item = $xmldoc->createElement('item');
11510
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
11511
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
11512
                $my_item->setAttribute('isvisible', 'true');
11513
                // Give a child element <title> to the <item> element.
11514
                $my_title = $xmldoc->createElement(
11515
                    'title',
11516
                    htmlspecialchars(
11517
                        api_utf8_encode($item->get_title()),
11518
                        ENT_QUOTES,
11519
                        'UTF-8'
11520
                    )
11521
                );
11522
                $my_item->appendChild($my_title);
11523
                // Give a child element <adlcp:prerequisites> to the <item> element.
11524
                $my_prereqs = $xmldoc->createElement(
11525
                    'adlcp:prerequisites',
11526
                    $this->get_scorm_prereq_string($my_item_id)
11527
                );
11528
                $my_prereqs->setAttribute('type', 'aicc_script');
11529
                $my_item->appendChild($my_prereqs);
11530
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
11531
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
11532
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
11533
                //$xmldoc->createElement('adlcp:timelimitaction','');
11534
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
11535
                //$xmldoc->createElement('adlcp:datafromlms','');
11536
                // Give a child element <adlcp:masteryscore> to the <item> element.
11537
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11538
                $my_item->appendChild($my_masteryscore);
11539
11540
                // Attach this item to the organization element or hits parent if there is one.
11541
                if (!empty($item->parent) && $item->parent != 0) {
11542
                    $children = $organization->childNodes;
11543
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11544
                    if (is_object($possible_parent)) {
11545
                        $possible_parent->appendChild($my_item);
11546
                    } else {
11547
                        if ($this->debug > 0) {
11548
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
11549
                        }
11550
                    }
11551
                } else {
11552
                    if ($this->debug > 0) {
11553
                        error_log('No parent');
11554
                    }
11555
                    $organization->appendChild($my_item);
11556
                }
11557
11558
                // Get the path of the file(s) from the course directory root.
11559
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11560
                $my_xml_file_path = $my_file_path;
11561
                if (!empty($path_to_remove)) {
11562
                    // From docs
11563
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
11564
11565
                    // From quiz
11566
                    if ($this->ref === 'chamilo_scorm_export') {
11567
                        $path_to_remove = 'scorm/'.$this->path.'/';
11568
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
11569
                    }
11570
                }
11571
11572
                $my_sub_dir = dirname($my_file_path);
11573
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11574
                $my_xml_sub_dir = $my_sub_dir;
11575
                // Give a <resource> child to the <resources> element
11576
                $my_resource = $xmldoc->createElement('resource');
11577
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11578
                $my_resource->setAttribute('type', 'webcontent');
11579
                $my_resource->setAttribute('href', $my_xml_file_path);
11580
                // adlcp:scormtype can be either 'sco' or 'asset'.
11581
                if ($item->type === 'sco') {
11582
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
11583
                } else {
11584
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
11585
                }
11586
                // xml:base is the base directory to find the files declared in this resource.
11587
                $my_resource->setAttribute('xml:base', '');
11588
                // Give a <file> child to the <resource> element.
11589
                $my_file = $xmldoc->createElement('file');
11590
                $my_file->setAttribute('href', $my_xml_file_path);
11591
                $my_resource->appendChild($my_file);
11592
11593
                // Dependency to other files - not yet supported.
11594
                $i = 1;
11595
                if ($inc_docs) {
11596
                    foreach ($inc_docs as $doc_info) {
11597
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
11598
                            continue;
11599
                        }
11600
                        $my_dep = $xmldoc->createElement('resource');
11601
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11602
                        $my_dep->setAttribute('identifier', $res_id);
11603
                        $my_dep->setAttribute('type', 'webcontent');
11604
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
11605
                        $my_dep_file = $xmldoc->createElement('file');
11606
                        // Check type of URL.
11607
                        if ($doc_info[1] == 'remote') {
11608
                            // Remote file. Save url as is.
11609
                            $my_dep_file->setAttribute('href', $doc_info[0]);
11610
                            $my_dep->setAttribute('xml:base', '');
11611
                        } elseif ($doc_info[1] === 'local') {
11612
                            switch ($doc_info[2]) {
11613
                                case 'url':
11614
                                    // Local URL - save path as url for now, don't zip file.
11615
                                    $abs_path = api_get_path(SYS_PATH).
11616
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11617
                                    $current_dir = dirname($abs_path);
11618
                                    $current_dir = str_replace('\\', '/', $current_dir);
11619
                                    $file_path = realpath($abs_path);
11620
                                    $file_path = str_replace('\\', '/', $file_path);
11621
                                    $my_dep_file->setAttribute('href', $file_path);
11622
                                    $my_dep->setAttribute('xml:base', '');
11623
                                    if (strstr($file_path, $main_path) !== false) {
11624
                                        // The calculated real path is really inside Chamilo's root path.
11625
                                        // Reduce file path to what's under the DocumentRoot.
11626
                                        $replace = $file_path;
11627
                                        $file_path = substr($file_path, strlen($root_path) - 1);
11628
                                        $destinationFile = $file_path;
11629
11630
                                        if (strstr($file_path, 'upload/users') !== false) {
11631
                                            $pos = strpos($file_path, 'my_files/');
11632
                                            if ($pos !== false) {
11633
                                                $onlyDirectory = str_replace(
11634
                                                    'upload/users/',
11635
                                                    '',
11636
                                                    substr($file_path, $pos, strlen($file_path))
11637
                                                );
11638
                                            }
11639
                                            $replace = './'.$onlyDirectory;
11640
                                            $destinationFile = $replace;
11641
                                        }
11642
11643
                                        if (strpos($file_path, '/web') === 0) {
11644
                                            $replace = str_replace('/web', 'web', $file_path);
11645
                                        }
11646
11647
                                        $zip_files_abs[] = $file_path;
11648
                                        $link_updates[$my_file_path][] = [
11649
                                            'orig' => $doc_info[0],
11650
                                            'dest' => $destinationFile,
11651
                                            'replace' => $replace,
11652
                                        ];
11653
                                        $my_dep_file->setAttribute('href', $file_path);
11654
                                        $my_dep->setAttribute('xml:base', '');
11655
                                    } elseif (empty($file_path)) {
11656
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11657
                                        $file_path = str_replace('//', '/', $file_path);
11658
                                        if (file_exists($file_path)) {
11659
                                            // We get the relative path.
11660
                                            $file_path = substr($file_path, strlen($current_dir));
11661
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
11662
                                            $link_updates[$my_file_path][] = [
11663
                                                'orig' => $doc_info[0],
11664
                                                'dest' => $file_path,
11665
                                            ];
11666
                                            $my_dep_file->setAttribute('href', $file_path);
11667
                                            $my_dep->setAttribute('xml:base', '');
11668
                                        }
11669
                                    }
11670
                                    break;
11671
                                case 'abs':
11672
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11673
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11674
                                    $my_dep->setAttribute('xml:base', '');
11675
11676
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
11677
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
11678
                                    $abs_img_path_without_subdir = $doc_info[0];
11679
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
11680
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
11681
                                    if ($pos === 0) {
11682
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
11683
                                    }
11684
11685
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
11686
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
11687
11688
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
11689
                                    $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
11690
                                    // Check if the current document is in that path.
11691
                                    if (strstr($file_path, $cur_path) !== false) {
11692
                                        $destinationFile = substr($file_path, strlen($cur_path));
11693
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
11694
11695
                                        $fileToTest = $cur_path.$my_file_path;
11696
                                        if (!empty($path_to_remove)) {
11697
                                            $fileToTest = str_replace(
11698
                                                $path_to_remove.'/',
11699
                                                $path_to_replace,
11700
                                                $cur_path.$my_file_path
11701
                                            );
11702
                                        }
11703
11704
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
11705
11706
                                        // Put the current document in the zip (this array is the array
11707
                                        // that will manage documents already in the course folder - relative).
11708
                                        $zip_files[] = $filePathNoCoursePart;
11709
                                        // Update the links to the current document in the
11710
                                        // containing document (make them relative).
11711
                                        $link_updates[$my_file_path][] = [
11712
                                            'orig' => $doc_info[0],
11713
                                            'dest' => $destinationFile,
11714
                                            'replace' => $relative_path,
11715
                                        ];
11716
11717
                                        $my_dep_file->setAttribute('href', $file_path);
11718
                                        $my_dep->setAttribute('xml:base', '');
11719
                                    } elseif (strstr($file_path, $main_path) !== false) {
11720
                                        // The calculated real path is really inside Chamilo's root path.
11721
                                        // Reduce file path to what's under the DocumentRoot.
11722
                                        $file_path = substr($file_path, strlen($root_path));
11723
                                        $zip_files_abs[] = $file_path;
11724
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11725
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11726
                                        $my_dep->setAttribute('xml:base', '');
11727
                                    } elseif (empty($file_path)) {
11728
                                        // Probably this is an image inside "/main" directory
11729
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
11730
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11731
11732
                                        if (file_exists($file_path)) {
11733
                                            $pos = strpos($file_path, 'main/default_course_document/');
11734
                                            if ($pos !== false) {
11735
                                                // We get the relative path.
11736
                                                $onlyDirectory = str_replace(
11737
                                                    'main/default_course_document/',
11738
                                                    '',
11739
                                                    substr($file_path, $pos, strlen($file_path))
11740
                                                );
11741
11742
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
11743
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
11744
                                                $zip_files_abs[] = $fileAbs;
11745
                                                $link_updates[$my_file_path][] = [
11746
                                                    'orig' => $doc_info[0],
11747
                                                    'dest' => $destinationFile,
11748
                                                ];
11749
                                                $my_dep_file->setAttribute('href', 'document/'.$destinationFile);
11750
                                                $my_dep->setAttribute('xml:base', '');
11751
                                            }
11752
                                        }
11753
                                    }
11754
                                    break;
11755
                                case 'rel':
11756
                                    // Path relative to the current document.
11757
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
11758
                                    if (substr($doc_info[0], 0, 2) === '..') {
11759
                                        // Relative path going up.
11760
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11761
                                        $current_dir = str_replace('\\', '/', $current_dir);
11762
                                        $file_path = realpath($current_dir.$doc_info[0]);
11763
                                        $file_path = str_replace('\\', '/', $file_path);
11764
                                        if (strstr($file_path, $main_path) !== false) {
11765
                                            // The calculated real path is really inside Chamilo's root path.
11766
                                            // Reduce file path to what's under the DocumentRoot.
11767
                                            $file_path = substr($file_path, strlen($root_path));
11768
                                            $zip_files_abs[] = $file_path;
11769
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11770
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11771
                                            $my_dep->setAttribute('xml:base', '');
11772
                                        }
11773
                                    } else {
11774
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11775
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
11776
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11777
                                    }
11778
                                    break;
11779
                                default:
11780
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11781
                                    $my_dep->setAttribute('xml:base', '');
11782
                                    break;
11783
                            }
11784
                        }
11785
                        $my_dep->appendChild($my_dep_file);
11786
                        $resources->appendChild($my_dep);
11787
                        $dependency = $xmldoc->createElement('dependency');
11788
                        $dependency->setAttribute('identifierref', $res_id);
11789
                        $my_resource->appendChild($dependency);
11790
                        $i++;
11791
                    }
11792
                }
11793
                $resources->appendChild($my_resource);
11794
                $zip_files[] = $my_file_path;
11795
            } else {
11796
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
11797
                switch ($item->type) {
11798
                    case TOOL_LINK:
11799
                        $my_item = $xmldoc->createElement('item');
11800
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11801
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11802
                        $my_item->setAttribute('isvisible', 'true');
11803
                        // Give a child element <title> to the <item> element.
11804
                        $my_title = $xmldoc->createElement(
11805
                            'title',
11806
                            htmlspecialchars(
11807
                                api_utf8_encode($item->get_title()),
11808
                                ENT_QUOTES,
11809
                                'UTF-8'
11810
                            )
11811
                        );
11812
                        $my_item->appendChild($my_title);
11813
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11814
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11815
                        $my_prereqs->setAttribute('type', 'aicc_script');
11816
                        $my_item->appendChild($my_prereqs);
11817
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
11818
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
11819
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
11820
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
11821
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
11822
                        //$xmldoc->createElement('adlcp:datafromlms', '');
11823
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11824
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11825
                        $my_item->appendChild($my_masteryscore);
11826
11827
                        // Attach this item to the organization element or its parent if there is one.
11828
                        if (!empty($item->parent) && $item->parent != 0) {
11829
                            $children = $organization->childNodes;
11830
                            for ($i = 0; $i < $children->length; $i++) {
11831
                                $item_temp = $children->item($i);
11832
                                if ($item_temp->nodeName == 'item') {
11833
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
11834
                                        $item_temp->appendChild($my_item);
11835
                                    }
11836
                                }
11837
                            }
11838
                        } else {
11839
                            $organization->appendChild($my_item);
11840
                        }
11841
11842
                        $my_file_path = 'link_'.$item->get_id().'.html';
11843
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
11844
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
11845
                        $rs = Database::query($sql);
11846
                        if ($link = Database::fetch_array($rs)) {
11847
                            $url = $link['url'];
11848
                            $title = stripslashes($link['title']);
11849
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
11850
                            $my_xml_file_path = $my_file_path;
11851
                            $my_sub_dir = dirname($my_file_path);
11852
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11853
                            $my_xml_sub_dir = $my_sub_dir;
11854
                            // Give a <resource> child to the <resources> element.
11855
                            $my_resource = $xmldoc->createElement('resource');
11856
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11857
                            $my_resource->setAttribute('type', 'webcontent');
11858
                            $my_resource->setAttribute('href', $my_xml_file_path);
11859
                            // adlcp:scormtype can be either 'sco' or 'asset'.
11860
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
11861
                            // xml:base is the base directory to find the files declared in this resource.
11862
                            $my_resource->setAttribute('xml:base', '');
11863
                            // give a <file> child to the <resource> element.
11864
                            $my_file = $xmldoc->createElement('file');
11865
                            $my_file->setAttribute('href', $my_xml_file_path);
11866
                            $my_resource->appendChild($my_file);
11867
                            $resources->appendChild($my_resource);
11868
                        }
11869
                        break;
11870
                    case TOOL_QUIZ:
11871
                        $exe_id = $item->path;
11872
                        // Should be using ref when everything will be cleaned up in this regard.
11873
                        $exe = new Exercise();
11874
                        $exe->read($exe_id);
11875
                        $my_item = $xmldoc->createElement('item');
11876
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11877
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11878
                        $my_item->setAttribute('isvisible', 'true');
11879
                        // Give a child element <title> to the <item> element.
11880
                        $my_title = $xmldoc->createElement(
11881
                            'title',
11882
                            htmlspecialchars(
11883
                                api_utf8_encode($item->get_title()),
11884
                                ENT_QUOTES,
11885
                                'UTF-8'
11886
                            )
11887
                        );
11888
                        $my_item->appendChild($my_title);
11889
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
11890
                        $my_item->appendChild($my_max_score);
11891
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11892
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11893
                        $my_prereqs->setAttribute('type', 'aicc_script');
11894
                        $my_item->appendChild($my_prereqs);
11895
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11896
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11897
                        $my_item->appendChild($my_masteryscore);
11898
11899
                        // Attach this item to the organization element or hits parent if there is one.
11900
                        if (!empty($item->parent) && $item->parent != 0) {
11901
                            $children = $organization->childNodes;
11902
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11903
                            if ($possible_parent) {
11904
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
11905
                                    $possible_parent->appendChild($my_item);
11906
                                }
11907
                            }
11908
                        } else {
11909
                            $organization->appendChild($my_item);
11910
                        }
11911
11912
                        // Get the path of the file(s) from the course directory root
11913
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11914
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
11915
                        // Write the contents of the exported exercise into a (big) html file
11916
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
11917
                        $scormExercise = new ScormExercise($exe, true);
11918
                        $contents = $scormExercise->export();
11919
11920
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
11921
                        $res = file_put_contents($tmp_file_path, $contents);
11922
                        if ($res === false) {
11923
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
11924
                        }
11925
                        $files_cleanup[] = $tmp_file_path;
11926
                        $my_xml_file_path = $my_file_path;
11927
                        $my_sub_dir = dirname($my_file_path);
11928
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11929
                        $my_xml_sub_dir = $my_sub_dir;
11930
                        // Give a <resource> child to the <resources> element.
11931
                        $my_resource = $xmldoc->createElement('resource');
11932
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11933
                        $my_resource->setAttribute('type', 'webcontent');
11934
                        $my_resource->setAttribute('href', $my_xml_file_path);
11935
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11936
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
11937
                        // xml:base is the base directory to find the files declared in this resource.
11938
                        $my_resource->setAttribute('xml:base', '');
11939
                        // Give a <file> child to the <resource> element.
11940
                        $my_file = $xmldoc->createElement('file');
11941
                        $my_file->setAttribute('href', $my_xml_file_path);
11942
                        $my_resource->appendChild($my_file);
11943
11944
                        // Get included docs.
11945
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
11946
11947
                        // Dependency to other files - not yet supported.
11948
                        $i = 1;
11949
                        foreach ($inc_docs as $doc_info) {
11950
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
11951
                                continue;
11952
                            }
11953
                            $my_dep = $xmldoc->createElement('resource');
11954
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11955
                            $my_dep->setAttribute('identifier', $res_id);
11956
                            $my_dep->setAttribute('type', 'webcontent');
11957
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
11958
                            $my_dep_file = $xmldoc->createElement('file');
11959
                            // Check type of URL.
11960
                            if ($doc_info[1] == 'remote') {
11961
                                // Remote file. Save url as is.
11962
                                $my_dep_file->setAttribute('href', $doc_info[0]);
11963
                                $my_dep->setAttribute('xml:base', '');
11964
                            } elseif ($doc_info[1] == 'local') {
11965
                                switch ($doc_info[2]) {
11966
                                    case 'url': // Local URL - save path as url for now, don't zip file.
11967
                                        // Save file but as local file (retrieve from URL).
11968
                                        $abs_path = api_get_path(SYS_PATH).
11969
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11970
                                        $current_dir = dirname($abs_path);
11971
                                        $current_dir = str_replace('\\', '/', $current_dir);
11972
                                        $file_path = realpath($abs_path);
11973
                                        $file_path = str_replace('\\', '/', $file_path);
11974
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11975
                                        $my_dep->setAttribute('xml:base', '');
11976
                                        if (strstr($file_path, $main_path) !== false) {
11977
                                            // The calculated real path is really inside the chamilo root path.
11978
                                            // Reduce file path to what's under the DocumentRoot.
11979
                                            $file_path = substr($file_path, strlen($root_path));
11980
                                            $zip_files_abs[] = $file_path;
11981
                                            $link_updates[$my_file_path][] = [
11982
                                                'orig' => $doc_info[0],
11983
                                                'dest' => 'document/'.$file_path,
11984
                                            ];
11985
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11986
                                            $my_dep->setAttribute('xml:base', '');
11987
                                        } elseif (empty($file_path)) {
11988
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11989
                                            $file_path = str_replace('//', '/', $file_path);
11990
                                            if (file_exists($file_path)) {
11991
                                                $file_path = substr($file_path, strlen($current_dir));
11992
                                                // We get the relative path.
11993
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11994
                                                $link_updates[$my_file_path][] = [
11995
                                                    'orig' => $doc_info[0],
11996
                                                    'dest' => 'document/'.$file_path,
11997
                                                ];
11998
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11999
                                                $my_dep->setAttribute('xml:base', '');
12000
                                            }
12001
                                        }
12002
                                        break;
12003
                                    case 'abs':
12004
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
12005
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
12006
                                        $current_dir = str_replace('\\', '/', $current_dir);
12007
                                        $file_path = realpath($doc_info[0]);
12008
                                        $file_path = str_replace('\\', '/', $file_path);
12009
                                        $my_dep_file->setAttribute('href', $file_path);
12010
                                        $my_dep->setAttribute('xml:base', '');
12011
12012
                                        if (strstr($file_path, $main_path) !== false) {
12013
                                            // The calculated real path is really inside the chamilo root path.
12014
                                            // Reduce file path to what's under the DocumentRoot.
12015
                                            $file_path = substr($file_path, strlen($root_path));
12016
                                            $zip_files_abs[] = $file_path;
12017
                                            $link_updates[$my_file_path][] = [
12018
                                                'orig' => $doc_info[0],
12019
                                                'dest' => $file_path,
12020
                                            ];
12021
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
12022
                                            $my_dep->setAttribute('xml:base', '');
12023
                                        } elseif (empty($file_path)) {
12024
                                            $docSysPartPath = str_replace(
12025
                                                api_get_path(REL_COURSE_PATH),
12026
                                                '',
12027
                                                $doc_info[0]
12028
                                            );
12029
12030
                                            $docSysPartPathNoCourseCode = str_replace(
12031
                                                $_course['directory'].'/',
12032
                                                '',
12033
                                                $docSysPartPath
12034
                                            );
12035
12036
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
12037
                                            if (file_exists($docSysPath)) {
12038
                                                $file_path = $docSysPartPathNoCourseCode;
12039
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
12040
                                                $link_updates[$my_file_path][] = [
12041
                                                    'orig' => $doc_info[0],
12042
                                                    'dest' => $file_path,
12043
                                                ];
12044
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
12045
                                                $my_dep->setAttribute('xml:base', '');
12046
                                            }
12047
                                        }
12048
                                        break;
12049
                                    case 'rel':
12050
                                        // Path relative to the current document. Save xml:base as current document's
12051
                                        // directory and save file in zip as subdir.file_path
12052
                                        if (substr($doc_info[0], 0, 2) === '..') {
12053
                                            // Relative path going up.
12054
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
12055
                                            $current_dir = str_replace('\\', '/', $current_dir);
12056
                                            $file_path = realpath($current_dir.$doc_info[0]);
12057
                                            $file_path = str_replace('\\', '/', $file_path);
12058
                                            if (strstr($file_path, $main_path) !== false) {
12059
                                                // The calculated real path is really inside Chamilo's root path.
12060
                                                // Reduce file path to what's under the DocumentRoot.
12061
12062
                                                $file_path = substr($file_path, strlen($root_path));
12063
                                                $file_path_dest = $file_path;
12064
12065
                                                // File path is courses/CHAMILO/document/....
12066
                                                $info_file_path = explode('/', $file_path);
12067
                                                if ($info_file_path[0] == 'courses') {
12068
                                                    // Add character "/" in file path.
12069
                                                    $file_path_dest = 'document/'.$file_path;
12070
                                                }
12071
                                                $zip_files_abs[] = $file_path;
12072
12073
                                                $link_updates[$my_file_path][] = [
12074
                                                    'orig' => $doc_info[0],
12075
                                                    'dest' => $file_path_dest,
12076
                                                ];
12077
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
12078
                                                $my_dep->setAttribute('xml:base', '');
12079
                                            }
12080
                                        } else {
12081
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
12082
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
12083
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
12084
                                        }
12085
                                        break;
12086
                                    default:
12087
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
12088
                                        $my_dep->setAttribute('xml:base', '');
12089
                                        break;
12090
                                }
12091
                            }
12092
                            $my_dep->appendChild($my_dep_file);
12093
                            $resources->appendChild($my_dep);
12094
                            $dependency = $xmldoc->createElement('dependency');
12095
                            $dependency->setAttribute('identifierref', $res_id);
12096
                            $my_resource->appendChild($dependency);
12097
                            $i++;
12098
                        }
12099
                        $resources->appendChild($my_resource);
12100
                        $zip_files[] = $my_file_path;
12101
                        break;
12102
                    default:
12103
                        // Get the path of the file(s) from the course directory root
12104
                        $my_file_path = 'non_exportable.html';
12105
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
12106
                        $my_xml_file_path = $my_file_path;
12107
                        $my_sub_dir = dirname($my_file_path);
12108
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
12109
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
12110
                        $my_xml_sub_dir = $my_sub_dir;
12111
                        // Give a <resource> child to the <resources> element.
12112
                        $my_resource = $xmldoc->createElement('resource');
12113
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
12114
                        $my_resource->setAttribute('type', 'webcontent');
12115
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
12116
                        // adlcp:scormtype can be either 'sco' or 'asset'.
12117
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
12118
                        // xml:base is the base directory to find the files declared in this resource.
12119
                        $my_resource->setAttribute('xml:base', '');
12120
                        // Give a <file> child to the <resource> element.
12121
                        $my_file = $xmldoc->createElement('file');
12122
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
12123
                        $my_resource->appendChild($my_file);
12124
                        $resources->appendChild($my_resource);
12125
                        break;
12126
                }
12127
            }
12128
        }
12129
        $organizations->appendChild($organization);
12130
        $root->appendChild($organizations);
12131
        $root->appendChild($resources);
12132
        $xmldoc->appendChild($root);
12133
12134
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
12135
12136
        // then add the file to the zip, then destroy the file (this is done automatically).
12137
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
12138
        foreach ($zip_files as $file_path) {
12139
            if (empty($file_path)) {
12140
                continue;
12141
            }
12142
12143
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
12144
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
12145
12146
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
12147
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
12148
            }
12149
12150
            $this->create_path($dest_file);
12151
            @copy($filePath, $dest_file);
12152
12153
            // Check if the file needs a link update.
12154
            if (in_array($file_path, array_keys($link_updates))) {
12155
                $string = file_get_contents($dest_file);
12156
                unlink($dest_file);
12157
                foreach ($link_updates[$file_path] as $old_new) {
12158
                    // This is an ugly hack that allows .flv files to be found by the flv player that
12159
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
12160
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
12161
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
12162
                    if (substr($old_new['dest'], -3) === 'flv' &&
12163
                        substr($old_new['dest'], 0, 5) === 'main/'
12164
                    ) {
12165
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
12166
                    } elseif (substr($old_new['dest'], -3) === 'flv' &&
12167
                        substr($old_new['dest'], 0, 6) === 'video/'
12168
                    ) {
12169
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
12170
                    }
12171
12172
                    // Fix to avoid problems with default_course_document
12173
                    if (strpos('main/default_course_document', $old_new['dest']) === false) {
12174
                        $newDestination = $old_new['dest'];
12175
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
12176
                            $newDestination = $old_new['replace'];
12177
                        }
12178
                    } else {
12179
                        $newDestination = str_replace('document/', '', $old_new['dest']);
12180
                    }
12181
                    $string = str_replace($old_new['orig'], $newDestination, $string);
12182
12183
                    // Add files inside the HTMLs
12184
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
12185
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
12186
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
12187
                        copy(
12188
                            $sys_course_path.$new_path,
12189
                            $destinationFile
12190
                        );
12191
                    }
12192
                }
12193
                file_put_contents($dest_file, $string);
12194
            }
12195
12196
            if (file_exists($filePath) && $copyAll) {
12197
                $extension = $this->get_extension($filePath);
12198
                if (in_array($extension, ['html', 'html'])) {
12199
                    $containerOrigin = dirname($filePath);
12200
                    $containerDestination = dirname($dest_file);
12201
12202
                    $finder = new Finder();
12203
                    $finder->files()->in($containerOrigin)
12204
                        ->notName('*_DELETED_*')
12205
                        ->exclude('share_folder')
12206
                        ->exclude('chat_files')
12207
                        ->exclude('certificates')
12208
                    ;
12209
12210
                    if (is_dir($containerOrigin) &&
12211
                        is_dir($containerDestination)
12212
                    ) {
12213
                        $fs = new Filesystem();
12214
                        $fs->mirror(
12215
                            $containerOrigin,
12216
                            $containerDestination,
12217
                            $finder
12218
                        );
12219
                    }
12220
                }
12221
            }
12222
        }
12223
12224
        foreach ($zip_files_abs as $file_path) {
12225
            if (empty($file_path)) {
12226
                continue;
12227
            }
12228
12229
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
12230
                continue;
12231
            }
12232
12233
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
12234
            if (strstr($file_path, 'upload/users') !== false) {
12235
                $pos = strpos($file_path, 'my_files/');
12236
                if ($pos !== false) {
12237
                    $onlyDirectory = str_replace(
12238
                        'upload/users/',
12239
                        '',
12240
                        substr($file_path, $pos, strlen($file_path))
12241
                    );
12242
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
12243
                }
12244
            }
12245
12246
            if (strstr($file_path, 'default_course_document/') !== false) {
12247
                $replace = str_replace('/main', '', $file_path);
12248
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
12249
            }
12250
12251
            if (empty($dest_file)) {
12252
                continue;
12253
            }
12254
12255
            $this->create_path($dest_file);
12256
            copy($main_path.$file_path, $dest_file);
12257
            // Check if the file needs a link update.
12258
            if (in_array($file_path, array_keys($link_updates))) {
12259
                $string = file_get_contents($dest_file);
12260
                unlink($dest_file);
12261
                foreach ($link_updates[$file_path] as $old_new) {
12262
                    // This is an ugly hack that allows .flv files to be found by the flv player that
12263
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
12264
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
12265
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
12266
                    if (substr($old_new['dest'], -3) == 'flv' &&
12267
                        substr($old_new['dest'], 0, 5) == 'main/'
12268
                    ) {
12269
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
12270
                    }
12271
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
12272
                }
12273
                file_put_contents($dest_file, $string);
12274
            }
12275
        }
12276
12277
        if (is_array($links_to_create)) {
12278
            foreach ($links_to_create as $file => $link) {
12279
                $content = '<!DOCTYPE html><head>
12280
                            <meta charset="'.api_get_language_isocode().'" />
12281
                            <title>'.$link['title'].'</title>
12282
                            </head>
12283
                            <body dir="'.api_get_text_direction().'">
12284
                            <div style="text-align:center">
12285
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
12286
                            </body>
12287
                            </html>';
12288
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
12289
            }
12290
        }
12291
12292
        // Add non exportable message explanation.
12293
        $lang_not_exportable = get_lang('ThisItemIsNotExportable');
12294
        $file_content = '<!DOCTYPE html><head>
12295
                        <meta charset="'.api_get_language_isocode().'" />
12296
                        <title>'.$lang_not_exportable.'</title>
12297
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
12298
                        </head>
12299
                        <body dir="'.api_get_text_direction().'">';
12300
        $file_content .=
12301
            <<<EOD
12302
                    <style>
12303
            .error-message {
12304
                font-family: arial, verdana, helvetica, sans-serif;
12305
                border-width: 1px;
12306
                border-style: solid;
12307
                left: 50%;
12308
                margin: 10px auto;
12309
                min-height: 30px;
12310
                padding: 5px;
12311
                right: 50%;
12312
                width: 500px;
12313
                background-color: #FFD1D1;
12314
                border-color: #FF0000;
12315
                color: #000;
12316
            }
12317
        </style>
12318
    <body>
12319
        <div class="error-message">
12320
            $lang_not_exportable
12321
        </div>
12322
    </body>
12323
</html>
12324
EOD;
12325
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
12326
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
12327
        }
12328
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
12329
12330
        // Add the extra files that go along with a SCORM package.
12331
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
12332
12333
        $fs = new Filesystem();
12334
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
12335
12336
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
12337
        $manifest = @$xmldoc->saveXML();
12338
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
12339
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
12340
12341
        $htmlIndex = new LpIndexGenerator($this);
12342
12343
        file_put_contents(
12344
            $archivePath.'/'.$temp_dir_short.'/index.html',
12345
            $htmlIndex->generate()
12346
        );
12347
12348
        $zip_folder->add(
12349
            $archivePath.'/'.$temp_dir_short,
12350
            PCLZIP_OPT_REMOVE_PATH,
12351
            $archivePath.'/'.$temp_dir_short.'/'
12352
        );
12353
12354
        // Clean possible temporary files.
12355
        foreach ($files_cleanup as $file) {
12356
            $res = unlink($file);
12357
            if ($res === false) {
12358
                error_log(
12359
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
12360
                    0
12361
                );
12362
            }
12363
        }
12364
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
12365
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
12366
    }
12367
12368
    /**
12369
     * @param int $lp_id
12370
     *
12371
     * @return bool
12372
     */
12373
    public function scorm_export_to_pdf($lp_id)
12374
    {
12375
        $lp_id = (int) $lp_id;
12376
        $files_to_export = [];
12377
12378
        $sessionId = api_get_session_id();
12379
        $course_data = api_get_course_info($this->cc);
12380
12381
        if (!empty($course_data)) {
12382
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
12383
            $list = self::get_flat_ordered_items_list($lp_id);
12384
            if (!empty($list)) {
12385
                foreach ($list as $item_id) {
12386
                    $item = $this->items[$item_id];
12387
                    switch ($item->type) {
12388
                        case 'document':
12389
                            // Getting documents from a LP with chamilo documents
12390
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
12391
                            // Try loading document from the base course.
12392
                            if (empty($file_data) && !empty($sessionId)) {
12393
                                $file_data = DocumentManager::get_document_data_by_id(
12394
                                    $item->path,
12395
                                    $this->cc,
12396
                                    false,
12397
                                    0
12398
                                );
12399
                            }
12400
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
12401
                            if (file_exists($file_path)) {
12402
                                $files_to_export[] = [
12403
                                    'title' => $item->get_title(),
12404
                                    'path' => $file_path,
12405
                                ];
12406
                            }
12407
                            break;
12408
                        case 'asset': //commes from a scorm package generated by chamilo
12409
                        case 'sco':
12410
                            $file_path = $scorm_path.'/'.$item->path;
12411
                            if (file_exists($file_path)) {
12412
                                $files_to_export[] = [
12413
                                    'title' => $item->get_title(),
12414
                                    'path' => $file_path,
12415
                                ];
12416
                            }
12417
                            break;
12418
                        case 'dir':
12419
                            $files_to_export[] = [
12420
                                'title' => $item->get_title(),
12421
                                'path' => null,
12422
                            ];
12423
                            break;
12424
                    }
12425
                }
12426
            }
12427
12428
            $pdf = new PDF();
12429
            $result = $pdf->html_to_pdf(
12430
                $files_to_export,
12431
                $this->name,
12432
                $this->cc,
12433
                true,
12434
                true,
12435
                true,
12436
                $this->get_name()
12437
            );
12438
12439
            return $result;
12440
        }
12441
12442
        return false;
12443
    }
12444
12445
    /**
12446
     * Temp function to be moved in main_api or the best place around for this.
12447
     * Creates a file path if it doesn't exist.
12448
     *
12449
     * @param string $path
12450
     */
12451
    public function create_path($path)
12452
    {
12453
        $path_bits = explode('/', dirname($path));
12454
12455
        // IS_WINDOWS_OS has been defined in main_api.lib.php
12456
        $path_built = IS_WINDOWS_OS ? '' : '/';
12457
        foreach ($path_bits as $bit) {
12458
            if (!empty($bit)) {
12459
                $new_path = $path_built.$bit;
12460
                if (is_dir($new_path)) {
12461
                    $path_built = $new_path.'/';
12462
                } else {
12463
                    mkdir($new_path, api_get_permissions_for_new_directories());
12464
                    $path_built = $new_path.'/';
12465
                }
12466
            }
12467
        }
12468
    }
12469
12470
    /**
12471
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
12472
     *
12473
     * @return bool The results of the unlink function, or false if there was no image to start with
12474
     */
12475
    public function delete_lp_image()
12476
    {
12477
        $img = $this->get_preview_image();
12478
        if ($img != '') {
12479
            $del_file = $this->get_preview_image_path(null, 'sys');
12480
            if (isset($del_file) && file_exists($del_file)) {
12481
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
12482
                if (file_exists($del_file_2)) {
12483
                    unlink($del_file_2);
12484
                }
12485
                $this->set_preview_image('');
12486
12487
                return @unlink($del_file);
12488
            }
12489
        }
12490
12491
        return false;
12492
    }
12493
12494
    /**
12495
     * Uploads an author image to the upload/learning_path/images directory.
12496
     *
12497
     * @param array    The image array, coming from the $_FILES superglobal
12498
     *
12499
     * @return bool True on success, false on error
12500
     */
12501
    public function upload_image($image_array)
12502
    {
12503
        if (!empty($image_array['name'])) {
12504
            $upload_ok = process_uploaded_file($image_array);
12505
            $has_attachment = true;
12506
        }
12507
12508
        if ($upload_ok && $has_attachment) {
12509
            $courseDir = api_get_course_path().'/upload/learning_path/images';
12510
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
12511
            $updir = $sys_course_path.$courseDir;
12512
            // Try to add an extension to the file if it hasn't one.
12513
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
12514
12515
            if (filter_extension($new_file_name)) {
12516
                $file_extension = explode('.', $image_array['name']);
12517
                $file_extension = strtolower($file_extension[count($file_extension) - 1]);
12518
                $filename = uniqid('');
12519
                $new_file_name = $filename.'.'.$file_extension;
12520
                $new_path = $updir.'/'.$new_file_name;
12521
12522
                // Resize the image.
12523
                $temp = new Image($image_array['tmp_name']);
12524
                $temp->resize(104);
12525
                $result = $temp->send_image($new_path);
12526
12527
                // Storing the image filename.
12528
                if ($result) {
12529
                    $this->set_preview_image($new_file_name);
12530
12531
                    //Resize to 64px to use on course homepage
12532
                    $temp->resize(64);
12533
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
12534
12535
                    return true;
12536
                }
12537
            }
12538
        }
12539
12540
        return false;
12541
    }
12542
12543
    /**
12544
     * @param int    $lp_id
12545
     * @param string $status
12546
     */
12547
    public function set_autolaunch($lp_id, $status)
12548
    {
12549
        $course_id = api_get_course_int_id();
12550
        $lp_id = (int) $lp_id;
12551
        $status = (int) $status;
12552
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
12553
12554
        // Setting everything to autolaunch = 0
12555
        $attributes['autolaunch'] = 0;
12556
        $where = [
12557
            'session_id = ? AND c_id = ? ' => [
12558
                api_get_session_id(),
12559
                $course_id,
12560
            ],
12561
        ];
12562
        Database::update($lp_table, $attributes, $where);
12563
        if ($status == 1) {
12564
            //Setting my lp_id to autolaunch = 1
12565
            $attributes['autolaunch'] = 1;
12566
            $where = [
12567
                'iid = ? AND session_id = ? AND c_id = ?' => [
12568
                    $lp_id,
12569
                    api_get_session_id(),
12570
                    $course_id,
12571
                ],
12572
            ];
12573
            Database::update($lp_table, $attributes, $where);
12574
        }
12575
    }
12576
12577
    /**
12578
     * Gets previous_item_id for the next element of the lp_item table.
12579
     *
12580
     * @author Isaac flores paz
12581
     *
12582
     * @return int Previous item ID
12583
     */
12584
    public function select_previous_item_id()
12585
    {
12586
        $course_id = api_get_course_int_id();
12587
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12588
12589
        // Get the max order of the items
12590
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
12591
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
12592
        $rs_max_order = Database::query($sql);
12593
        $row_max_order = Database::fetch_object($rs_max_order);
12594
        $max_order = $row_max_order->display_order;
12595
        // Get the previous item ID
12596
        $sql = "SELECT iid as previous FROM $table_lp_item
12597
                WHERE
12598
                    c_id = $course_id AND
12599
                    lp_id = ".$this->lp_id." AND
12600
                    display_order = '$max_order' ";
12601
        $rs_max = Database::query($sql);
12602
        $row_max = Database::fetch_object($rs_max);
12603
12604
        // Return the previous item ID
12605
        return $row_max->previous;
12606
    }
12607
12608
    /**
12609
     * Copies an LP.
12610
     */
12611
    public function copy()
12612
    {
12613
        // Course builder
12614
        $cb = new CourseBuilder();
12615
12616
        //Setting tools that will be copied
12617
        $cb->set_tools_to_build(['learnpaths']);
12618
12619
        //Setting elements that will be copied
12620
        $cb->set_tools_specific_id_list(['learnpaths' => [$this->lp_id]]);
12621
12622
        $course = $cb->build();
12623
12624
        //Course restorer
12625
        $course_restorer = new CourseRestorer($course);
12626
        $course_restorer->set_add_text_in_items(true);
12627
        $course_restorer->set_tool_copy_settings(
12628
            ['learnpaths' => ['reset_dates' => true]]
12629
        );
12630
        $course_restorer->restore(
12631
            api_get_course_id(),
12632
            api_get_session_id(),
12633
            false,
12634
            false
12635
        );
12636
    }
12637
12638
    /**
12639
     * Verify document size.
12640
     *
12641
     * @param string $s
12642
     *
12643
     * @return bool
12644
     */
12645
    public static function verify_document_size($s)
12646
    {
12647
        $post_max = ini_get('post_max_size');
12648
        if (substr($post_max, -1, 1) == 'M') {
12649
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
12650
        } elseif (substr($post_max, -1, 1) == 'G') {
12651
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
12652
        }
12653
        $upl_max = ini_get('upload_max_filesize');
12654
        if (substr($upl_max, -1, 1) == 'M') {
12655
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
12656
        } elseif (substr($upl_max, -1, 1) == 'G') {
12657
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
12658
        }
12659
        $documents_total_space = DocumentManager::documents_total_space();
12660
        $course_max_space = DocumentManager::get_course_quota();
12661
        $total_size = filesize($s) + $documents_total_space;
12662
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
12663
            return true;
12664
        }
12665
12666
        return false;
12667
    }
12668
12669
    /**
12670
     * Clear LP prerequisites.
12671
     */
12672
    public function clear_prerequisites()
12673
    {
12674
        $course_id = $this->get_course_int_id();
12675
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12676
        $lp_id = $this->get_id();
12677
        // Cleaning prerequisites
12678
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
12679
                WHERE c_id = $course_id AND lp_id = $lp_id";
12680
        Database::query($sql);
12681
12682
        // Cleaning mastery score for exercises
12683
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
12684
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
12685
        Database::query($sql);
12686
    }
12687
12688
    public function set_previous_step_as_prerequisite_for_all_items()
12689
    {
12690
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12691
        $course_id = $this->get_course_int_id();
12692
        $lp_id = $this->get_id();
12693
12694
        if (!empty($this->items)) {
12695
            $previous_item_id = null;
12696
            $previous_item_max = 0;
12697
            $previous_item_type = null;
12698
            $last_item_not_dir = null;
12699
            $last_item_not_dir_type = null;
12700
            $last_item_not_dir_max = null;
12701
12702
            foreach ($this->ordered_items as $itemId) {
12703
                $item = $this->getItem($itemId);
12704
                // if there was a previous item... (otherwise jump to set it)
12705
                if (!empty($previous_item_id)) {
12706
                    $current_item_id = $item->get_id(); //save current id
12707
                    if ($item->get_type() != 'dir') {
12708
                        // Current item is not a folder, so it qualifies to get a prerequisites
12709
                        if ($last_item_not_dir_type == 'quiz') {
12710
                            // if previous is quiz, mark its max score as default score to be achieved
12711
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
12712
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
12713
                            Database::query($sql);
12714
                        }
12715
                        // now simply update the prerequisite to set it to the last non-chapter item
12716
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
12717
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
12718
                        Database::query($sql);
12719
                        // record item as 'non-chapter' reference
12720
                        $last_item_not_dir = $item->get_id();
12721
                        $last_item_not_dir_type = $item->get_type();
12722
                        $last_item_not_dir_max = $item->get_max();
12723
                    }
12724
                } else {
12725
                    if ($item->get_type() != 'dir') {
12726
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
12727
                        $last_item_not_dir = $item->get_id();
12728
                        $last_item_not_dir_type = $item->get_type();
12729
                        $last_item_not_dir_max = $item->get_max();
12730
                    }
12731
                }
12732
                // Saving the item as "previous item" for the next loop
12733
                $previous_item_id = $item->get_id();
12734
                $previous_item_max = $item->get_max();
12735
                $previous_item_type = $item->get_type();
12736
            }
12737
        }
12738
    }
12739
12740
    /**
12741
     * @param array $params
12742
     *
12743
     * @throws \Doctrine\ORM\OptimisticLockException
12744
     *
12745
     * @return int
12746
     */
12747
    public static function createCategory($params)
12748
    {
12749
        $em = Database::getManager();
12750
        $item = new CLpCategory();
12751
        $item->setName($params['name']);
12752
        $item->setCId($params['c_id']);
12753
        $em->persist($item);
12754
        $em->flush();
12755
12756
        $id = $item->getId();
12757
12758
        $sessionId = api_get_session_id();
12759
        if (!empty($sessionId) && api_get_configuration_value('allow_session_lp_category')) {
12760
            $table = Database::get_course_table(TABLE_LP_CATEGORY);
12761
            $sql = "UPDATE $table SET session_id = $sessionId WHERE iid = $id";
12762
            Database::query($sql);
12763
        }
12764
12765
        api_item_property_update(
12766
            api_get_course_info(),
12767
            TOOL_LEARNPATH_CATEGORY,
12768
            $id,
12769
            'visible',
12770
            api_get_user_id()
12771
        );
12772
12773
        return $item->getId();
12774
    }
12775
12776
    /**
12777
     * @param array $params
12778
     */
12779
    public static function updateCategory($params)
12780
    {
12781
        $em = Database::getManager();
12782
        $item = self::getCategory($params['id']);
12783
12784
        if ($item) {
12785
            $item->setName($params['name']);
12786
            $em->persist($item);
12787
            $em->flush();
12788
        }
12789
    }
12790
12791
    /**
12792
     * @param int $id
12793
     */
12794
    public static function moveUpCategory($id)
12795
    {
12796
        $item = self::getCategory($id);
12797
        if ($item) {
12798
            $em = Database::getManager();
12799
            $position = $item->getPosition() - 1;
12800
            $item->setPosition($position);
12801
            $em->persist($item);
12802
            $em->flush();
12803
        }
12804
    }
12805
12806
    /**
12807
     * @param int $id
12808
     *
12809
     * @throws \Doctrine\ORM\ORMException
12810
     * @throws \Doctrine\ORM\OptimisticLockException
12811
     * @throws \Doctrine\ORM\TransactionRequiredException
12812
     */
12813
    public static function moveDownCategory($id)
12814
    {
12815
        $item = self::getCategory($id);
12816
        if ($item) {
12817
            $em = Database::getManager();
12818
            $position = $item->getPosition() + 1;
12819
            $item->setPosition($position);
12820
            $em->persist($item);
12821
            $em->flush();
12822
        }
12823
    }
12824
12825
    public static function getLpList($courseId, $sessionId, $onlyActiveLp = true)
12826
    {
12827
        $TABLE_LP = Database::get_course_table(TABLE_LP_MAIN);
12828
        $TABLE_ITEM_PROPERTY = Database::get_course_table(TABLE_ITEM_PROPERTY);
12829
        $courseId = (int) $courseId;
12830
        $sessionId = (int) $sessionId;
12831
12832
        $sql = "SELECT lp.id, lp.name
12833
                FROM $TABLE_LP lp
12834
                INNER JOIN $TABLE_ITEM_PROPERTY ip
12835
                ON lp.id = ip.ref
12836
                WHERE lp.c_id = $courseId ";
12837
12838
        if (!empty($sessionId)) {
12839
            $sql .= "AND ip.session_id = $sessionId ";
12840
        }
12841
12842
        if ($onlyActiveLp) {
12843
            $sql .= "AND ip.tool = 'learnpath' ";
12844
            $sql .= "AND ip.visibility = 1 ";
12845
        }
12846
12847
        $sql .= "GROUP BY lp.id";
12848
12849
        $result = Database::query($sql);
12850
12851
        return Database::store_result($result, 'ASSOC');
12852
    }
12853
12854
    /**
12855
     * @param int $courseId
12856
     *
12857
     * @throws \Doctrine\ORM\Query\QueryException
12858
     *
12859
     * @return int|mixed
12860
     */
12861
    public static function getCountCategories($courseId)
12862
    {
12863
        if (empty($courseId)) {
12864
            return 0;
12865
        }
12866
        $em = Database::getManager();
12867
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
12868
        $query->setParameter('id', $courseId);
12869
12870
        return $query->getSingleScalarResult();
12871
    }
12872
12873
    /**
12874
     * @param int $courseId
12875
     *
12876
     * @return array|CLpCategory[]
12877
     */
12878
    public static function getCategories($courseId, $withNoneCategory = false)
12879
    {
12880
        $em = Database::getManager();
12881
12882
        // Using doctrine extensions
12883
        /** @var SortableRepository $repo */
12884
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
12885
12886
        $categories = $repo->getBySortableGroupsQuery(['cId' => $courseId])->getResult();
12887
12888
        if ($withNoneCategory) {
12889
            $categoryTest = new CLpCategory();
12890
            $categoryTest->setId(0)
12891
                ->setName(get_lang('WithOutCategory'))
12892
                ->setPosition(0);
12893
12894
            array_unshift($categories, $categoryTest);
12895
        }
12896
12897
        return $categories;
12898
    }
12899
12900
    public static function getCategorySessionId($id)
12901
    {
12902
        if (false === api_get_configuration_value('allow_session_lp_category')) {
12903
            return 0;
12904
        }
12905
12906
        $table = Database::get_course_table(TABLE_LP_CATEGORY);
12907
        $id = (int) $id;
12908
12909
        $sql = "SELECT session_id FROM $table WHERE iid = $id";
12910
        $result = Database::query($sql);
12911
        $result = Database::fetch_array($result, 'ASSOC');
12912
12913
        if ($result) {
12914
            return (int) $result['session_id'];
12915
        }
12916
12917
        return 0;
12918
    }
12919
12920
    /**
12921
     * @param int $id
12922
     *
12923
     * @return CLpCategory
12924
     */
12925
    public static function getCategory($id)
12926
    {
12927
        $id = (int) $id;
12928
        $em = Database::getManager();
12929
12930
        return $em->find('ChamiloCourseBundle:CLpCategory', $id);
12931
    }
12932
12933
    /**
12934
     * @param int $courseId
12935
     *
12936
     * @return CLpCategory[]
12937
     */
12938
    public static function getCategoryByCourse($courseId)
12939
    {
12940
        $em = Database::getManager();
12941
12942
        return $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(['cId' => $courseId]);
12943
    }
12944
12945
    /**
12946
     * @param int $id
12947
     *
12948
     * @return bool
12949
     */
12950
    public static function deleteCategory($id)
12951
    {
12952
        $em = Database::getManager();
12953
        $id = (int) $id;
12954
        $item = self::getCategory($id);
12955
        if ($item) {
12956
            $courseId = $item->getCId();
12957
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
12958
            $query->setParameter('id', $courseId);
12959
            $query->setParameter('catId', $item->getId());
12960
            $lps = $query->getResult();
12961
12962
            // Setting category = 0.
12963
            if ($lps) {
12964
                foreach ($lps as $lpItem) {
12965
                    $lpItem->setCategoryId(0);
12966
                }
12967
            }
12968
12969
            if (api_get_configuration_value('allow_lp_subscription_to_usergroups')) {
12970
                $table = Database::get_course_table(TABLE_LP_CATEGORY_REL_USERGROUP);
12971
                $sql = "DELETE FROM $table
12972
                        WHERE
12973
                            lp_category_id = $id AND
12974
                            c_id = $courseId ";
12975
                Database::query($sql);
12976
            }
12977
12978
            // Removing category.
12979
            $em->remove($item);
12980
            $em->flush();
12981
12982
            $courseInfo = api_get_course_info_by_id($courseId);
12983
            $sessionId = api_get_session_id();
12984
12985
            // Delete link tool
12986
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
12987
            $linkLikeValue = "lp/lp_controller.php?%&action=view_category&id=$id%";
12988
            // Delete tools
12989
            $sql = "DELETE FROM $tbl_tool
12990
                    WHERE c_id = ".$courseId." AND (link LIKE '$linkLikeValue' AND image='lp_category.gif')";
12991
            Database::query($sql);
12992
12993
            return true;
12994
        }
12995
12996
        return false;
12997
    }
12998
12999
    /**
13000
     * @param int  $courseId
13001
     * @param bool $addSelectOption
13002
     *
13003
     * @return mixed
13004
     */
13005
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
13006
    {
13007
        $items = self::getCategoryByCourse($courseId);
13008
        $cats = [];
13009
        if ($addSelectOption) {
13010
            $cats = [get_lang('SelectACategory')];
13011
        }
13012
13013
        $sessionId = api_get_session_id();
13014
        $avoidCategoryInSession = false;
13015
        if (empty($sessionId)) {
13016
            $avoidCategoryInSession = true;
13017
        }
13018
        /*$checkSession = false;
13019
        if (api_get_configuration_value('allow_session_lp_category')) {
13020
            $checkSession = true;
13021
        }*/
13022
13023
        if (!empty($items)) {
13024
            foreach ($items as $cat) {
13025
                $categoryId = $cat->getId();
13026
                if ($avoidCategoryInSession) {
13027
                    $inSession = self::getCategorySessionId($categoryId);
13028
                    if (!empty($inSession)) {
13029
                        continue;
13030
                    }
13031
                }
13032
                $cats[$categoryId] = $cat->getName();
13033
            }
13034
        }
13035
13036
        return $cats;
13037
    }
13038
13039
    /**
13040
     * @param string $courseCode
13041
     * @param int    $lpId
13042
     * @param int    $user_id
13043
     *
13044
     * @return learnpath
13045
     */
13046
    public static function getLpFromSession($courseCode, $lpId, $user_id)
13047
    {
13048
        $debug = 0;
13049
        $learnPath = null;
13050
        $lpObject = Session::read('lpobject');
13051
        if ($lpObject !== null) {
13052
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
13053
            if ($debug) {
13054
                error_log('getLpFromSession: unserialize');
13055
                error_log('------getLpFromSession------');
13056
                error_log('------unserialize------');
13057
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
13058
                error_log("api_get_sessionid: ".api_get_session_id());
13059
            }
13060
        }
13061
13062
        if (!is_object($learnPath)) {
13063
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
13064
            if ($debug) {
13065
                error_log('------getLpFromSession------');
13066
                error_log('getLpFromSession: create new learnpath');
13067
                error_log("create new LP with $courseCode - $lpId - $user_id");
13068
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
13069
                error_log("api_get_sessionid: ".api_get_session_id());
13070
            }
13071
        }
13072
13073
        return $learnPath;
13074
    }
13075
13076
    /**
13077
     * @param int $itemId
13078
     *
13079
     * @return learnpathItem|false
13080
     */
13081
    public function getItem($itemId)
13082
    {
13083
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
13084
            return $this->items[$itemId];
13085
        }
13086
13087
        return false;
13088
    }
13089
13090
    /**
13091
     * @return int
13092
     */
13093
    public function getCurrentAttempt()
13094
    {
13095
        $attempt = $this->getItem($this->get_current_item_id());
13096
        if ($attempt) {
13097
            $attemptId = $attempt->get_attempt_id();
13098
13099
            return $attemptId;
13100
        }
13101
13102
        return 0;
13103
    }
13104
13105
    /**
13106
     * @return int
13107
     */
13108
    public function getCategoryId()
13109
    {
13110
        return (int) $this->categoryId;
13111
    }
13112
13113
    /**
13114
     * @param int $categoryId
13115
     *
13116
     * @return bool
13117
     */
13118
    public function setCategoryId($categoryId)
13119
    {
13120
        $this->categoryId = (int) $categoryId;
13121
        $table = Database::get_course_table(TABLE_LP_MAIN);
13122
        $lp_id = $this->get_id();
13123
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
13124
                WHERE iid = $lp_id";
13125
        Database::query($sql);
13126
13127
        return true;
13128
    }
13129
13130
    /**
13131
     * Get whether this is a learning path with the possibility to subscribe
13132
     * users or not.
13133
     *
13134
     * @return int
13135
     */
13136
    public function getSubscribeUsers()
13137
    {
13138
        return $this->subscribeUsers;
13139
    }
13140
13141
    /**
13142
     * Set whether this is a learning path with the possibility to subscribe
13143
     * users or not.
13144
     *
13145
     * @param int $value (0 = false, 1 = true)
13146
     *
13147
     * @return bool
13148
     */
13149
    public function setSubscribeUsers($value)
13150
    {
13151
        $this->subscribeUsers = (int) $value;
13152
        $table = Database::get_course_table(TABLE_LP_MAIN);
13153
        $lp_id = $this->get_id();
13154
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
13155
                WHERE iid = $lp_id";
13156
        Database::query($sql);
13157
13158
        return true;
13159
    }
13160
13161
    /**
13162
     * Calculate the count of stars for a user in this LP
13163
     * This calculation is based on the following rules:
13164
     * - the student gets one star when he gets to 50% of the learning path
13165
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
13166
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
13167
     * - the student gets the final star when the score for the *last* test is >= 80%.
13168
     *
13169
     * @param int $sessionId Optional. The session ID
13170
     *
13171
     * @return int The count of stars
13172
     */
13173
    public function getCalculateStars($sessionId = 0)
13174
    {
13175
        $stars = 0;
13176
        $progress = self::getProgress(
13177
            $this->lp_id,
13178
            $this->user_id,
13179
            $this->course_int_id,
13180
            $sessionId
13181
        );
13182
13183
        if ($progress >= 50) {
13184
            $stars++;
13185
        }
13186
13187
        // Calculate stars chapters evaluation
13188
        $exercisesItems = $this->getExercisesItems();
13189
13190
        if (!empty($exercisesItems)) {
13191
            $totalResult = 0;
13192
13193
            foreach ($exercisesItems as $exerciseItem) {
13194
                $exerciseResultInfo = Event::getExerciseResultsByUser(
13195
                    $this->user_id,
13196
                    $exerciseItem->path,
13197
                    $this->course_int_id,
13198
                    $sessionId,
13199
                    $this->lp_id,
13200
                    $exerciseItem->db_id
13201
                );
13202
13203
                $exerciseResultInfo = end($exerciseResultInfo);
13204
13205
                if (!$exerciseResultInfo) {
13206
                    continue;
13207
                }
13208
13209
                if (!empty($exerciseResultInfo['exe_weighting'])) {
13210
                    $exerciseResult = $exerciseResultInfo['exe_result'] * 100 / $exerciseResultInfo['exe_weighting'];
13211
                } else {
13212
                    $exerciseResult = 0;
13213
                }
13214
                $totalResult += $exerciseResult;
13215
            }
13216
13217
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
13218
13219
            if ($totalExerciseAverage >= 50) {
13220
                $stars++;
13221
            }
13222
13223
            if ($totalExerciseAverage >= 80) {
13224
                $stars++;
13225
            }
13226
        }
13227
13228
        // Calculate star for final evaluation
13229
        $finalEvaluationItem = $this->getFinalEvaluationItem();
13230
13231
        if (!empty($finalEvaluationItem)) {
13232
            $evaluationResultInfo = Event::getExerciseResultsByUser(
13233
                $this->user_id,
13234
                $finalEvaluationItem->path,
13235
                $this->course_int_id,
13236
                $sessionId,
13237
                $this->lp_id,
13238
                $finalEvaluationItem->db_id
13239
            );
13240
13241
            $evaluationResultInfo = end($evaluationResultInfo);
13242
13243
            if ($evaluationResultInfo) {
13244
                $evaluationResult = $evaluationResultInfo['exe_result'] * 100 / $evaluationResultInfo['exe_weighting'];
13245
13246
                if ($evaluationResult >= 80) {
13247
                    $stars++;
13248
                }
13249
            }
13250
        }
13251
13252
        return $stars;
13253
    }
13254
13255
    /**
13256
     * Get the items of exercise type.
13257
     *
13258
     * @return array The items. Otherwise return false
13259
     */
13260
    public function getExercisesItems()
13261
    {
13262
        $exercises = [];
13263
        foreach ($this->items as $item) {
13264
            if ($item->type != 'quiz') {
13265
                continue;
13266
            }
13267
            $exercises[] = $item;
13268
        }
13269
13270
        array_pop($exercises);
13271
13272
        return $exercises;
13273
    }
13274
13275
    /**
13276
     * Get the item of exercise type (evaluation type).
13277
     *
13278
     * @return array The final evaluation. Otherwise return false
13279
     */
13280
    public function getFinalEvaluationItem()
13281
    {
13282
        $exercises = [];
13283
        foreach ($this->items as $item) {
13284
            if ($item->type != 'quiz') {
13285
                continue;
13286
            }
13287
13288
            $exercises[] = $item;
13289
        }
13290
13291
        return array_pop($exercises);
13292
    }
13293
13294
    /**
13295
     * Calculate the total points achieved for the current user in this learning path.
13296
     *
13297
     * @param int $sessionId Optional. The session Id
13298
     *
13299
     * @return int
13300
     */
13301
    public function getCalculateScore($sessionId = 0)
13302
    {
13303
        // Calculate stars chapters evaluation
13304
        $exercisesItems = $this->getExercisesItems();
13305
        $finalEvaluationItem = $this->getFinalEvaluationItem();
13306
        $totalExercisesResult = 0;
13307
        $totalEvaluationResult = 0;
13308
13309
        if ($exercisesItems !== false) {
13310
            foreach ($exercisesItems as $exerciseItem) {
13311
                $exerciseResultInfo = Event::getExerciseResultsByUser(
13312
                    $this->user_id,
13313
                    $exerciseItem->path,
13314
                    $this->course_int_id,
13315
                    $sessionId,
13316
                    $this->lp_id,
13317
                    $exerciseItem->db_id
13318
                );
13319
13320
                $exerciseResultInfo = end($exerciseResultInfo);
13321
13322
                if (!$exerciseResultInfo) {
13323
                    continue;
13324
                }
13325
13326
                $totalExercisesResult += $exerciseResultInfo['exe_result'];
13327
            }
13328
        }
13329
13330
        if (!empty($finalEvaluationItem)) {
13331
            $evaluationResultInfo = Event::getExerciseResultsByUser(
13332
                $this->user_id,
13333
                $finalEvaluationItem->path,
13334
                $this->course_int_id,
13335
                $sessionId,
13336
                $this->lp_id,
13337
                $finalEvaluationItem->db_id
13338
            );
13339
13340
            $evaluationResultInfo = end($evaluationResultInfo);
13341
13342
            if ($evaluationResultInfo) {
13343
                $totalEvaluationResult += $evaluationResultInfo['exe_result'];
13344
            }
13345
        }
13346
13347
        return $totalExercisesResult + $totalEvaluationResult;
13348
    }
13349
13350
    /**
13351
     * Check if URL is not allowed to be show in a iframe.
13352
     *
13353
     * @param string $src
13354
     *
13355
     * @return string
13356
     */
13357
    public function fixBlockedLinks($src)
13358
    {
13359
        $urlInfo = parse_url($src);
13360
13361
        $platformProtocol = 'https';
13362
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
13363
            $platformProtocol = 'http';
13364
        }
13365
13366
        $protocolFixApplied = false;
13367
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
13368
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
13369
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
13370
13371
        if ($platformProtocol != $scheme) {
13372
            Session::write('x_frame_source', $src);
13373
            $src = 'blank.php?error=x_frames_options';
13374
            $protocolFixApplied = true;
13375
        }
13376
13377
        if ($protocolFixApplied == false) {
13378
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
13379
                // Check X-Frame-Options
13380
                $ch = curl_init();
13381
                $options = [
13382
                    CURLOPT_URL => $src,
13383
                    CURLOPT_RETURNTRANSFER => true,
13384
                    CURLOPT_HEADER => true,
13385
                    CURLOPT_FOLLOWLOCATION => true,
13386
                    CURLOPT_ENCODING => "",
13387
                    CURLOPT_AUTOREFERER => true,
13388
                    CURLOPT_CONNECTTIMEOUT => 120,
13389
                    CURLOPT_TIMEOUT => 120,
13390
                    CURLOPT_MAXREDIRS => 10,
13391
                ];
13392
13393
                $proxySettings = api_get_configuration_value('proxy_settings');
13394
                if (!empty($proxySettings) &&
13395
                    isset($proxySettings['curl_setopt_array'])
13396
                ) {
13397
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
13398
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
13399
                }
13400
13401
                curl_setopt_array($ch, $options);
13402
                $response = curl_exec($ch);
13403
                $httpCode = curl_getinfo($ch);
13404
                $headers = substr($response, 0, $httpCode['header_size']);
13405
13406
                $error = false;
13407
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
13408
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
13409
                ) {
13410
                    $error = true;
13411
                }
13412
13413
                if ($error) {
13414
                    Session::write('x_frame_source', $src);
13415
                    $src = 'blank.php?error=x_frames_options';
13416
                }
13417
            }
13418
        }
13419
13420
        return $src;
13421
    }
13422
13423
    /**
13424
     * Check if this LP has a created forum in the basis course.
13425
     *
13426
     * @return bool
13427
     */
13428
    public function lpHasForum()
13429
    {
13430
        $forumTable = Database::get_course_table(TABLE_FORUM);
13431
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
13432
13433
        $fakeFrom = "
13434
            $forumTable f
13435
            INNER JOIN $itemProperty ip
13436
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
13437
        ";
13438
13439
        $resultData = Database::select(
13440
            'COUNT(f.iid) AS qty',
13441
            $fakeFrom,
13442
            [
13443
                'where' => [
13444
                    'ip.visibility != ? AND ' => 2,
13445
                    'ip.tool = ? AND ' => TOOL_FORUM,
13446
                    'f.c_id = ? AND ' => intval($this->course_int_id),
13447
                    'f.lp_id = ?' => intval($this->lp_id),
13448
                ],
13449
            ],
13450
            'first'
13451
        );
13452
13453
        return $resultData['qty'] > 0;
13454
    }
13455
13456
    /**
13457
     * Get the forum for this learning path.
13458
     *
13459
     * @param int $sessionId
13460
     *
13461
     * @return bool
13462
     */
13463
    public function getForum($sessionId = 0)
13464
    {
13465
        $forumTable = Database::get_course_table(TABLE_FORUM);
13466
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
13467
13468
        $fakeFrom = "$forumTable f
13469
            INNER JOIN $itemProperty ip ";
13470
13471
        if ($this->lp_session_id == 0) {
13472
            $fakeFrom .= "
13473
                ON (
13474
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND (
13475
                        f.session_id = ip.session_id OR ip.session_id IS NULL
13476
                    )
13477
                )
13478
            ";
13479
        } else {
13480
            $fakeFrom .= "
13481
                ON (
13482
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND f.session_id = ip.session_id
13483
                )
13484
            ";
13485
        }
13486
13487
        $resultData = Database::select(
13488
            'f.*',
13489
            $fakeFrom,
13490
            [
13491
                'where' => [
13492
                    'ip.visibility != ? AND ' => 2,
13493
                    'ip.tool = ? AND ' => TOOL_FORUM,
13494
                    'f.session_id = ? AND ' => $sessionId,
13495
                    'f.c_id = ? AND ' => intval($this->course_int_id),
13496
                    'f.lp_id = ?' => intval($this->lp_id),
13497
                ],
13498
            ],
13499
            'first'
13500
        );
13501
13502
        if (empty($resultData)) {
13503
            return false;
13504
        }
13505
13506
        return $resultData;
13507
    }
13508
13509
    /**
13510
     * Create a forum for this learning path.
13511
     *
13512
     * @param int $forumCategoryId
13513
     *
13514
     * @return int The forum ID if was created. Otherwise return false
13515
     */
13516
    public function createForum($forumCategoryId)
13517
    {
13518
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
13519
13520
        $forumId = store_forum(
13521
            [
13522
                'lp_id' => $this->lp_id,
13523
                'forum_title' => $this->name,
13524
                'forum_comment' => null,
13525
                'forum_category' => (int) $forumCategoryId,
13526
                'students_can_edit_group' => ['students_can_edit' => 0],
13527
                'allow_new_threads_group' => ['allow_new_threads' => 0],
13528
                'default_view_type_group' => ['default_view_type' => 'flat'],
13529
                'group_forum' => 0,
13530
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
13531
            ],
13532
            [],
13533
            true
13534
        );
13535
13536
        return $forumId;
13537
    }
13538
13539
    /**
13540
     * Get the LP Final Item form.
13541
     *
13542
     * @throws Exception
13543
     * @throws HTML_QuickForm_Error
13544
     *
13545
     * @return string
13546
     */
13547
    public function getFinalItemForm()
13548
    {
13549
        $finalItem = $this->getFinalItem();
13550
        $title = '';
13551
13552
        if ($finalItem) {
13553
            $title = $finalItem->get_title();
13554
            $buttonText = get_lang('Save');
13555
            $content = $this->getSavedFinalItem();
13556
        } else {
13557
            $buttonText = get_lang('LPCreateDocument');
13558
            $content = $this->getFinalItemTemplate();
13559
        }
13560
13561
        $courseInfo = api_get_course_info();
13562
        $result = $this->generate_lp_folder($courseInfo);
13563
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
13564
        $relative_prefix = '../../';
13565
13566
        $editorConfig = [
13567
            'ToolbarSet' => 'LearningPathDocuments',
13568
            'Width' => '100%',
13569
            'Height' => '500',
13570
            'FullPage' => true,
13571
            'CreateDocumentDir' => $relative_prefix,
13572
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
13573
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
13574
        ];
13575
13576
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
13577
            'type' => 'document',
13578
            'lp_id' => $this->lp_id,
13579
        ]);
13580
13581
        $form = new FormValidator('final_item', 'POST', $url);
13582
        $form->addText('title', get_lang('Title'));
13583
        $form->addButtonSave($buttonText);
13584
13585
        $renderer = $form->defaultRenderer();
13586
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
13587
13588
        $form->addHtmlEditor(
13589
            'content_lp_certificate',
13590
            null,
13591
            true,
13592
            false,
13593
            $editorConfig,
13594
            true
13595
        );
13596
        $form->addHtml(
13597
            Display::return_message(
13598
                get_lang('LPEndStepAddTagsToShowCertificateOrSkillAutomatically').'</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
13599
                'normal',
13600
                false
13601
            )
13602
        );
13603
        $form->addHidden('action', 'add_final_item');
13604
        $form->addHidden('path', Session::read('pathItem'));
13605
        $form->addHidden('previous', $this->get_last());
13606
        $form->setDefaults(['title' => $title, 'content_lp_certificate' => $content]);
13607
13608
        if ($form->validate()) {
13609
            $values = $form->exportValues();
13610
            $lastItemId = $this->getLastInFirstLevel();
13611
13612
            if (!$finalItem) {
13613
                $documentId = $this->create_document(
13614
                    $this->course_info,
13615
                    $values['content_lp_certificate'],
13616
                    $values['title']
13617
                );
13618
                $this->add_item(
13619
                    0,
13620
                    $lastItemId,
13621
                    'final_item',
13622
                    $documentId,
13623
                    $values['title'],
13624
                    ''
13625
                );
13626
13627
                Display::addFlash(
13628
                    Display::return_message(get_lang('Added'))
13629
                );
13630
            } else {
13631
                $this->edit_document($this->course_info);
13632
            }
13633
        }
13634
13635
        return $form->returnForm();
13636
    }
13637
13638
    /**
13639
     * Check if the current lp item is first, both, last or none from lp list.
13640
     *
13641
     * @param int $currentItemId
13642
     *
13643
     * @return string
13644
     */
13645
    public function isFirstOrLastItem($currentItemId)
13646
    {
13647
        $lpItemId = [];
13648
        $typeListNotToVerify = self::getChapterTypes();
13649
13650
        // Using get_toc() function instead $this->items because returns the correct order of the items
13651
        foreach ($this->get_toc() as $item) {
13652
            if (!in_array($item['type'], $typeListNotToVerify)) {
13653
                $lpItemId[] = $item['id'];
13654
            }
13655
        }
13656
13657
        $lastLpItemIndex = count($lpItemId) - 1;
13658
        $position = array_search($currentItemId, $lpItemId);
13659
13660
        switch ($position) {
13661
            case 0:
13662
                if (!$lastLpItemIndex) {
13663
                    $answer = 'both';
13664
                    break;
13665
                }
13666
13667
                $answer = 'first';
13668
                break;
13669
            case $lastLpItemIndex:
13670
                $answer = 'last';
13671
                break;
13672
            default:
13673
                $answer = 'none';
13674
        }
13675
13676
        return $answer;
13677
    }
13678
13679
    /**
13680
     * Get whether this is a learning path with the accumulated SCORM time or not.
13681
     *
13682
     * @return int
13683
     */
13684
    public function getAccumulateScormTime()
13685
    {
13686
        return $this->accumulateScormTime;
13687
    }
13688
13689
    /**
13690
     * Set whether this is a learning path with the accumulated SCORM time or not.
13691
     *
13692
     * @param int $value (0 = false, 1 = true)
13693
     *
13694
     * @return bool Always returns true
13695
     */
13696
    public function setAccumulateScormTime($value)
13697
    {
13698
        $this->accumulateScormTime = (int) $value;
13699
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
13700
        $lp_id = $this->get_id();
13701
        $sql = "UPDATE $lp_table
13702
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
13703
                WHERE iid = $lp_id";
13704
        Database::query($sql);
13705
13706
        return true;
13707
    }
13708
13709
    /**
13710
     * Returns an HTML-formatted link to a resource, to incorporate directly into
13711
     * the new learning path tool.
13712
     *
13713
     * The function is a big switch on tool type.
13714
     * In each case, we query the corresponding table for information and build the link
13715
     * with that information.
13716
     *
13717
     * @author Yannick Warnier <[email protected]> - rebranding based on
13718
     * previous work (display_addedresource_link_in_learnpath())
13719
     *
13720
     * @param int $course_id      Course code
13721
     * @param int $learningPathId The learning path ID (in lp table)
13722
     * @param int $id_in_path     the unique index in the items table
13723
     * @param int $lpViewId
13724
     * @param int $lpSessionId
13725
     *
13726
     * @return string
13727
     */
13728
    public static function rl_get_resource_link_for_learnpath(
13729
        $course_id,
13730
        $learningPathId,
13731
        $id_in_path,
13732
        $lpViewId,
13733
        $lpSessionId = 0
13734
    ) {
13735
        $session_id = api_get_session_id();
13736
        $course_info = api_get_course_info_by_id($course_id);
13737
13738
        $learningPathId = (int) $learningPathId;
13739
        $id_in_path = (int) $id_in_path;
13740
        $lpViewId = (int) $lpViewId;
13741
13742
        $em = Database::getManager();
13743
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
13744
13745
        /** @var CLpItem $rowItem */
13746
        $rowItem = $lpItemRepo->findOneBy([
13747
            'cId' => $course_id,
13748
            'lpId' => $learningPathId,
13749
            'iid' => $id_in_path,
13750
        ]);
13751
13752
        if (!$rowItem) {
13753
            // Try one more time with "id"
13754
            /** @var CLpItem $rowItem */
13755
            $rowItem = $lpItemRepo->findOneBy([
13756
                'cId' => $course_id,
13757
                'lpId' => $learningPathId,
13758
                'id' => $id_in_path,
13759
            ]);
13760
13761
            if (!$rowItem) {
13762
                return -1;
13763
            }
13764
        }
13765
13766
        $course_code = $course_info['code'];
13767
        $type = $rowItem->getItemType();
13768
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
13769
        $main_dir_path = api_get_path(WEB_CODE_PATH);
13770
        $main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
13771
        $link = '';
13772
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
13773
        // It adds lti parameter
13774
        if (isset($_REQUEST['lti_launch_id'])) {
13775
            $extraParams .= '&lti_launch_id='.Security::remove_XSS($_REQUEST['lti_launch_id']);
13776
        }
13777
13778
        switch ($type) {
13779
            case 'dir':
13780
                return $main_dir_path.'lp/blank.php';
13781
            case TOOL_CALENDAR_EVENT:
13782
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
13783
            case TOOL_ANNOUNCEMENT:
13784
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
13785
            case TOOL_LINK:
13786
                $linkInfo = Link::getLinkInfo($id);
13787
                if ($linkInfo) {
13788
                    $itemPropertyInfo = api_get_item_property_info($course_id, TOOL_LINK, $id, $session_id);
13789
                    if ($itemPropertyInfo && 0 === (int) $itemPropertyInfo['visibility']) {
13790
                        return '';
13791
                    }
13792
                    if (isset($linkInfo['url'])) {
13793
                        return $linkInfo['url'];
13794
                    }
13795
                }
13796
13797
                return '';
13798
            case TOOL_QUIZ:
13799
                if (empty($id)) {
13800
                    return '';
13801
                }
13802
13803
                // Get the lp_item_view with the highest view_count.
13804
                $learnpathItemViewResult = $em
13805
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
13806
                    ->findBy(
13807
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
13808
                        ['viewCount' => 'DESC'],
13809
                        1
13810
                    );
13811
                /** @var CLpItemView $learnpathItemViewData */
13812
                $learnpathItemViewData = current($learnpathItemViewResult);
13813
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
13814
13815
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
13816
                    .http_build_query([
13817
                        'lp_init' => 1,
13818
                        'learnpath_item_view_id' => (int) $learnpathItemViewId,
13819
                        'learnpath_id' => (int) $learningPathId,
13820
                        'learnpath_item_id' => (int) $id_in_path,
13821
                        'exerciseId' => (int) $id,
13822
                    ]);
13823
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
13824
                $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
13825
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
13826
                $myrow = Database::fetch_array($result);
13827
                $path = $myrow['path'];
13828
13829
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
13830
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
13831
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;
13832
            case TOOL_FORUM:
13833
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
13834
            case TOOL_THREAD:
13835
                // forum post
13836
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
13837
                if (empty($id)) {
13838
                    return '';
13839
                }
13840
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
13841
                $result = Database::query($sql);
13842
                $myrow = Database::fetch_array($result);
13843
13844
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
13845
                    .$extraParams;
13846
            case TOOL_POST:
13847
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13848
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
13849
                $myrow = Database::fetch_array($result);
13850
13851
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
13852
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
13853
            case TOOL_READOUT_TEXT:
13854
                return api_get_path(WEB_CODE_PATH).
13855
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
13856
            case TOOL_DOCUMENT:
13857
                $repo = $em->getRepository('ChamiloCourseBundle:CDocument');
13858
                $document = $repo->findOneBy(['cId' => $course_id, 'iid' => $id]);
13859
13860
                if (empty($document)) {
13861
                    // Try with normal id
13862
                    $document = $repo->findOneBy(['cId' => $course_id, 'id' => $id]);
13863
13864
                    if (empty($document)) {
13865
                        return '';
13866
                    }
13867
                }
13868
13869
                $documentPathInfo = pathinfo($document->getPath());
13870
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v', 'webm', 'wav'];
13871
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
13872
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
13873
13874
                $openmethod = 2;
13875
                $officedoc = false;
13876
                Session::write('openmethod', $openmethod);
13877
                Session::write('officedoc', $officedoc);
13878
13879
                // Same validation as in document/download.php
13880
                $isVisible = DocumentManager::is_visible(
13881
                    $document->getPath(),
13882
                    api_get_course_info(),
13883
                    api_get_session_id()
13884
                );
13885
13886
                if (!api_is_allowed_to_edit() && !$isVisible) {
13887
                    return '';
13888
                }
13889
13890
                if ($showDirectUrl) {
13891
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
13892
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
13893
                        if (Link::isPdfLink($file)) {
13894
                            return api_get_path(WEB_LIBRARY_PATH).
13895
                                'javascript/ViewerJS/index.html?zoom=page-width#'.$file;
13896
                        }
13897
                    }
13898
13899
                    return $file;
13900
                }
13901
13902
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
13903
            case TOOL_LP_FINAL_ITEM:
13904
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
13905
                    .$extraParams;
13906
            case 'assignments':
13907
                return $main_dir_path.'work/work.php?'.$extraParams;
13908
            case TOOL_DROPBOX:
13909
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
13910
            case 'introduction_text': //DEPRECATED
13911
                return '';
13912
            case TOOL_COURSE_DESCRIPTION:
13913
                return $main_dir_path.'course_description?'.$extraParams;
13914
            case TOOL_GROUP:
13915
                return $main_dir_path.'group/group.php?'.$extraParams;
13916
            case TOOL_USER:
13917
                return $main_dir_path.'user/user.php?'.$extraParams;
13918
            case TOOL_STUDENTPUBLICATION:
13919
                if (!empty($rowItem->getPath())) {
13920
                    $workId = $rowItem->getPath();
13921
                    if (empty($lpSessionId) && !empty($session_id)) {
13922
                        // Check if a student publication with the same name exists in this session see BT#17700
13923
                        $title = Database::escape_string($rowItem->getTitle());
13924
                        $table = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
13925
                        $sql = "SELECT * FROM $table
13926
                                WHERE
13927
                                    active = 1 AND
13928
                                    parent_id = 0 AND
13929
                                    c_id = $course_id AND
13930
                                    session_id = $session_id AND
13931
                                    title = '$title'
13932
                                LIMIT 1";
13933
                        $result = Database::query($sql);
13934
                        if (Database::num_rows($result)) {
13935
                            $work = Database::fetch_array($result, 'ASSOC');
13936
                            if ($work) {
13937
                                $workId = $work['iid'];
13938
                            }
13939
                        }
13940
                    }
13941
13942
                    return $main_dir_path.'work/work_list.php?id='.$workId.'&'.$extraParams;
13943
                }
13944
13945
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
13946
            case TOOL_XAPI:
13947
                $toolLaunch = $em->find(ToolLaunch::class, $id);
13948
13949
                if (empty($toolLaunch)) {
13950
                    break;
13951
                }
13952
13953
                return api_get_path(WEB_PLUGIN_PATH).'xapi/'
13954
                    .('cmi5' === $toolLaunch->getActivityType() ? 'cmi5/view.php' : 'tincan/view.php')
13955
                    ."?id=$id&$extraParams";
13956
            case TOOL_H5P:
13957
                if ('true' === api_get_plugin_setting('h5pimport', 'tool_enable')){
13958
                    $toolLaunch = $em->find(H5pImport::class, $id);
13959
13960
                    if (empty($toolLaunch)) {
13961
                        break;
13962
                    }
13963
13964
                    return api_get_path(WEB_PLUGIN_PATH).'h5pimport/view.php'."?id=$id&$extraParams";
13965
                }
13966
                break;
13967
            case TOOL_SURVEY:
13968
                $table = Database::get_course_table(TABLE_SURVEY);
13969
                $sql = "SELECT code FROM $table WHERE c_id = $course_id AND survey_id =".(int) $id;
13970
                $result = Database::query($sql);
13971
                $surveyCode = Database::result($result, 0, 0);
13972
                $autoSurveyLink = SurveyUtil::generateFillSurveyLink(
13973
                    'auto',
13974
                    $course_info,
13975
                    api_get_session_id(),
13976
                    $surveyCode
13977
                );
13978
                $lpParams = [
13979
                    'lp_id' => $learningPathId,
13980
                    'lp_item_id' => $id_in_path,
13981
                    'origin' => 'learnpath',
13982
                ];
13983
13984
                return $autoSurveyLink.'&'.http_build_query($lpParams);
13985
        }
13986
13987
        return $link;
13988
    }
13989
13990
    /**
13991
     * Gets the name of a resource (generally used in learnpath when no name is provided).
13992
     *
13993
     * @author Yannick Warnier <[email protected]>
13994
     *
13995
     * @param string $course_code    Course code
13996
     * @param int    $learningPathId
13997
     * @param int    $id_in_path     The resource ID
13998
     *
13999
     * @return string
14000
     */
14001
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
14002
    {
14003
        $_course = api_get_course_info($course_code);
14004
        if (empty($_course)) {
14005
            return '';
14006
        }
14007
        $course_id = $_course['real_id'];
14008
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
14009
        $learningPathId = (int) $learningPathId;
14010
        $id_in_path = (int) $id_in_path;
14011
14012
        $sql = "SELECT item_type, title, ref
14013
                FROM $tbl_lp_item
14014
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
14015
        $res_item = Database::query($sql);
14016
14017
        if (Database::num_rows($res_item) < 1) {
14018
            return '';
14019
        }
14020
        $row_item = Database::fetch_array($res_item);
14021
        $type = strtolower($row_item['item_type']);
14022
        $id = $row_item['ref'];
14023
        $output = '';
14024
14025
        switch ($type) {
14026
            case TOOL_CALENDAR_EVENT:
14027
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
14028
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
14029
                $myrow = Database::fetch_array($result);
14030
                $output = $myrow['title'];
14031
                break;
14032
            case TOOL_ANNOUNCEMENT:
14033
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
14034
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
14035
                $myrow = Database::fetch_array($result);
14036
                $output = $myrow['title'];
14037
                break;
14038
            case TOOL_LINK:
14039
                // Doesn't take $target into account.
14040
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
14041
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
14042
                $myrow = Database::fetch_array($result);
14043
                $output = $myrow['title'];
14044
                break;
14045
            case TOOL_QUIZ:
14046
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
14047
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE iid = $id");
14048
                $myrow = Database::fetch_array($result);
14049
                $output = $myrow['title'];
14050
                break;
14051
            case TOOL_FORUM:
14052
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
14053
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
14054
                $myrow = Database::fetch_array($result);
14055
                $output = $myrow['forum_name'];
14056
                break;
14057
            case TOOL_THREAD:
14058
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
14059
                // Grabbing the title of the post.
14060
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
14061
                $result_title = Database::query($sql_title);
14062
                $myrow_title = Database::fetch_array($result_title);
14063
                $output = $myrow_title['post_title'];
14064
                break;
14065
            case TOOL_POST:
14066
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
14067
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
14068
                $result = Database::query($sql);
14069
                $post = Database::fetch_array($result);
14070
                $output = $post['post_title'];
14071
                break;
14072
            case 'dir':
14073
            case TOOL_DOCUMENT:
14074
                $title = $row_item['title'];
14075
                $output = '-';
14076
                if (!empty($title)) {
14077
                    $output = $title;
14078
                }
14079
                break;
14080
            case 'hotpotatoes':
14081
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
14082
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
14083
                $myrow = Database::fetch_array($result);
14084
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
14085
                $last = count($pathname) - 1; // Making a correct name for the link.
14086
                $filename = $pathname[$last]; // Making a correct name for the link.
14087
                $myrow['path'] = rawurlencode($myrow['path']);
14088
                $output = $filename;
14089
                break;
14090
        }
14091
14092
        return stripslashes($output);
14093
    }
14094
14095
    /**
14096
     * Get the parent names for the current item.
14097
     *
14098
     * @param int $newItemId Optional. The item ID
14099
     *
14100
     * @return array
14101
     */
14102
    public function getCurrentItemParentNames($newItemId = 0)
14103
    {
14104
        $newItemId = $newItemId ?: $this->get_current_item_id();
14105
        $return = [];
14106
        $item = $this->getItem($newItemId);
14107
        $parent = $this->getItem($item->get_parent());
14108
14109
        while ($parent) {
14110
            $return[] = $parent->get_title();
14111
            $parent = $this->getItem($parent->get_parent());
14112
        }
14113
14114
        return array_reverse($return);
14115
    }
14116
14117
    /**
14118
     * Reads and process "lp_subscription_settings" setting.
14119
     *
14120
     * @return array
14121
     */
14122
    public static function getSubscriptionSettings()
14123
    {
14124
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
14125
        if (empty($subscriptionSettings)) {
14126
            // By default allow both settings
14127
            $subscriptionSettings = [
14128
                'allow_add_users_to_lp' => true,
14129
                'allow_add_users_to_lp_category' => true,
14130
            ];
14131
        } else {
14132
            $subscriptionSettings = $subscriptionSettings['options'];
14133
        }
14134
14135
        return $subscriptionSettings;
14136
    }
14137
14138
    /**
14139
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
14140
     */
14141
    public function exportToCourseBuildFormat()
14142
    {
14143
        if (!api_is_allowed_to_edit()) {
14144
            return false;
14145
        }
14146
14147
        $courseBuilder = new CourseBuilder();
14148
        $itemList = [];
14149
        /** @var learnpathItem $item */
14150
        foreach ($this->items as $item) {
14151
            $itemList[$item->get_type()][] = $item->get_path();
14152
        }
14153
14154
        if (empty($itemList)) {
14155
            return false;
14156
        }
14157
14158
        if (isset($itemList['document'])) {
14159
            // Get parents
14160
            foreach ($itemList['document'] as $documentId) {
14161
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
14162
                if (!empty($documentInfo['parents'])) {
14163
                    foreach ($documentInfo['parents'] as $parentInfo) {
14164
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
14165
                            continue;
14166
                        }
14167
                        $itemList['document'][] = $parentInfo['iid'];
14168
                    }
14169
                }
14170
            }
14171
14172
            $courseInfo = api_get_course_info();
14173
            foreach ($itemList['document'] as $documentId) {
14174
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
14175
                $items = DocumentManager::get_resources_from_source_html(
14176
                    $documentInfo['absolute_path'],
14177
                    true,
14178
                    TOOL_DOCUMENT
14179
                );
14180
14181
                if (!empty($items)) {
14182
                    foreach ($items as $item) {
14183
                        // Get information about source url
14184
                        $url = $item[0]; // url
14185
                        $scope = $item[1]; // scope (local, remote)
14186
                        $type = $item[2]; // type (rel, abs, url)
14187
14188
                        $origParseUrl = parse_url($url);
14189
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
14190
14191
                        if ($scope == 'local') {
14192
                            if ($type == 'abs' || $type == 'rel') {
14193
                                $documentFile = strstr($realOrigPath, 'document');
14194
                                if (strpos($realOrigPath, $documentFile) !== false) {
14195
                                    $documentFile = str_replace('document', '', $documentFile);
14196
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
14197
                                    // Document found! Add it to the list
14198
                                    if ($itemDocumentId) {
14199
                                        $itemList['document'][] = $itemDocumentId;
14200
                                    }
14201
                                }
14202
                            }
14203
                        }
14204
                    }
14205
                }
14206
            }
14207
14208
            $courseBuilder->build_documents(
14209
                api_get_session_id(),
14210
                $this->get_course_int_id(),
14211
                true,
14212
                $itemList['document']
14213
            );
14214
        }
14215
14216
        if (isset($itemList['quiz'])) {
14217
            $courseBuilder->build_quizzes(
14218
                api_get_session_id(),
14219
                $this->get_course_int_id(),
14220
                true,
14221
                $itemList['quiz']
14222
            );
14223
        }
14224
14225
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
14226
14227
        /*if (!empty($itemList['thread'])) {
14228
            $postList = [];
14229
            foreach ($itemList['thread'] as $postId) {
14230
                $post = get_post_information($postId);
14231
                if ($post) {
14232
                    if (!isset($itemList['forum'])) {
14233
                        $itemList['forum'] = [];
14234
                    }
14235
                    $itemList['forum'][] = $post['forum_id'];
14236
                    $postList[] = $postId;
14237
                }
14238
            }
14239
14240
            if (!empty($postList)) {
14241
                $courseBuilder->build_forum_posts(
14242
                    $this->get_course_int_id(),
14243
                    null,
14244
                    null,
14245
                    $postList
14246
                );
14247
            }
14248
        }*/
14249
14250
        if (!empty($itemList['thread'])) {
14251
            $threadList = [];
14252
            $em = Database::getManager();
14253
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
14254
            foreach ($itemList['thread'] as $threadId) {
14255
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
14256
                $thread = $repo->find($threadId);
14257
                if ($thread) {
14258
                    $itemList['forum'][] = $thread->getForumId();
14259
                    $threadList[] = $thread->getIid();
14260
                }
14261
            }
14262
14263
            if (!empty($threadList)) {
14264
                $courseBuilder->build_forum_topics(
14265
                    api_get_session_id(),
14266
                    $this->get_course_int_id(),
14267
                    null,
14268
                    $threadList
14269
                );
14270
            }
14271
        }
14272
14273
        $forumCategoryList = [];
14274
        if (isset($itemList['forum'])) {
14275
            foreach ($itemList['forum'] as $forumId) {
14276
                $forumInfo = get_forums($forumId);
14277
                $forumCategoryList[] = $forumInfo['forum_category'];
14278
            }
14279
        }
14280
14281
        if (!empty($forumCategoryList)) {
14282
            $courseBuilder->build_forum_category(
14283
                api_get_session_id(),
14284
                $this->get_course_int_id(),
14285
                true,
14286
                $forumCategoryList
14287
            );
14288
        }
14289
14290
        if (!empty($itemList['forum'])) {
14291
            $courseBuilder->build_forums(
14292
                api_get_session_id(),
14293
                $this->get_course_int_id(),
14294
                true,
14295
                $itemList['forum']
14296
            );
14297
        }
14298
14299
        if (isset($itemList['link'])) {
14300
            $courseBuilder->build_links(
14301
                api_get_session_id(),
14302
                $this->get_course_int_id(),
14303
                true,
14304
                $itemList['link']
14305
            );
14306
        }
14307
14308
        if (!empty($itemList['student_publication'])) {
14309
            $courseBuilder->build_works(
14310
                api_get_session_id(),
14311
                $this->get_course_int_id(),
14312
                true,
14313
                $itemList['student_publication']
14314
            );
14315
        }
14316
14317
        $courseBuilder->build_learnpaths(
14318
            api_get_session_id(),
14319
            $this->get_course_int_id(),
14320
            true,
14321
            [$this->get_id()],
14322
            false
14323
        );
14324
14325
        $courseBuilder->restoreDocumentsFromList();
14326
14327
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
14328
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
14329
        $result = DocumentManager::file_send_for_download(
14330
            $zipPath,
14331
            true,
14332
            $this->get_name().'.zip'
14333
        );
14334
14335
        if ($result) {
14336
            api_not_allowed();
14337
        }
14338
14339
        return true;
14340
    }
14341
14342
    /**
14343
     * Get whether this is a learning path with the accumulated work time or not.
14344
     *
14345
     * @return int
14346
     */
14347
    public function getAccumulateWorkTime()
14348
    {
14349
        return (int) $this->accumulateWorkTime;
14350
    }
14351
14352
    /**
14353
     * Get whether this is a learning path with the accumulated work time or not.
14354
     *
14355
     * @return int
14356
     */
14357
    public function getAccumulateWorkTimeTotalCourse()
14358
    {
14359
        $table = Database::get_course_table(TABLE_LP_MAIN);
14360
        $sql = "SELECT SUM(accumulate_work_time) AS total
14361
                FROM $table
14362
                WHERE c_id = ".$this->course_int_id;
14363
        $result = Database::query($sql);
14364
        $row = Database::fetch_array($result);
14365
14366
        return (int) $row['total'];
14367
    }
14368
14369
    /**
14370
     * Set whether this is a learning path with the accumulated work time or not.
14371
     *
14372
     * @param int $value (0 = false, 1 = true)
14373
     *
14374
     * @return bool
14375
     */
14376
    public function setAccumulateWorkTime($value)
14377
    {
14378
        if (!api_get_configuration_value('lp_minimum_time')) {
14379
            return false;
14380
        }
14381
14382
        $this->accumulateWorkTime = (int) $value;
14383
        $table = Database::get_course_table(TABLE_LP_MAIN);
14384
        $lp_id = $this->get_id();
14385
        $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
14386
                WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
14387
        Database::query($sql);
14388
14389
        return true;
14390
    }
14391
14392
    /**
14393
     * @param int $lpId
14394
     * @param int $courseId
14395
     *
14396
     * @return mixed
14397
     */
14398
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
14399
    {
14400
        $lpId = (int) $lpId;
14401
        $courseId = (int) $courseId;
14402
14403
        $table = Database::get_course_table(TABLE_LP_MAIN);
14404
        $sql = "SELECT accumulate_work_time
14405
                FROM $table
14406
                WHERE c_id = $courseId AND id = $lpId";
14407
        $result = Database::query($sql);
14408
        $row = Database::fetch_array($result);
14409
14410
        return $row['accumulate_work_time'];
14411
    }
14412
14413
    /**
14414
     * @param int $courseId
14415
     *
14416
     * @return int
14417
     */
14418
    public static function getAccumulateWorkTimeTotal($courseId)
14419
    {
14420
        $table = Database::get_course_table(TABLE_LP_MAIN);
14421
        $courseId = (int) $courseId;
14422
        $sql = "SELECT SUM(accumulate_work_time) AS total
14423
                FROM $table
14424
                WHERE c_id = $courseId";
14425
        $result = Database::query($sql);
14426
        $row = Database::fetch_array($result);
14427
14428
        return (int) $row['total'];
14429
    }
14430
14431
    /**
14432
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
14433
     * and put the images in.
14434
     *
14435
     * @return array
14436
     */
14437
    public static function getIconSelect()
14438
    {
14439
        $theme = api_get_visual_theme();
14440
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
14441
        $icons = ['' => get_lang('SelectAnOption')];
14442
14443
        if (is_dir($path)) {
14444
            $finder = new Finder();
14445
            $finder->files()->in($path);
14446
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
14447
            /** @var SplFileInfo $file */
14448
            foreach ($finder as $file) {
14449
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
14450
                    $icons[$file->getFilename()] = $file->getFilename();
14451
                }
14452
            }
14453
        }
14454
14455
        return $icons;
14456
    }
14457
14458
    /**
14459
     * Get the learnpaths availables for next lp.
14460
     *
14461
     * @param int $courseId
14462
     *
14463
     * @return array
14464
     */
14465
    public static function getNextLpsAvailable($courseId, $lpId)
14466
    {
14467
        $table = Database::get_course_table(TABLE_LP_MAIN);
14468
14469
        $sqlNextLp = "SELECT next_lp_id FROM $table WHERE c_id = $courseId AND next_lp_id > 0";
14470
        $sql = "SELECT iid, name
14471
                FROM $table
14472
            WHERE c_id = $courseId AND
14473
                  iid NOT IN($sqlNextLp)";
14474
        $rs = Database::query($sql);
14475
        $lpsAvailable = [0 => get_lang('None')];
14476
        if (Database::num_rows($rs) > 0) {
14477
            while ($row = Database::fetch_array($rs)) {
14478
                if ($row['iid'] == $lpId) {
14479
                    continue;
14480
                }
14481
                $lpsAvailable[$row['iid']] = $row['name'];
14482
            }
14483
        }
14484
14485
        return $lpsAvailable;
14486
    }
14487
14488
    /**
14489
     * Get The next lp id.
14490
     *
14491
     * @param $lpId
14492
     * @param $courseId
14493
     *
14494
     * @return int
14495
     */
14496
    public static function getFlowNextLpId($lpId, $courseId)
14497
    {
14498
        $table = Database::get_course_table(TABLE_LP_MAIN);
14499
14500
        $sql = "SELECT next_lp_id
14501
                FROM $table
14502
            WHERE c_id = $courseId AND iid = $lpId";
14503
        $rs = Database::query($sql);
14504
        $nextLpId = Database::result($rs, 0, 0);
14505
14506
        return $nextLpId;
14507
    }
14508
14509
    /**
14510
     * Get The previous lp id.
14511
     *
14512
     * @param $nextLpId
14513
     * @param $courseId
14514
     *
14515
     * @return int
14516
     */
14517
    public static function getFlowPrevLpId($nextLpId, $courseId)
14518
    {
14519
        $table = Database::get_course_table(TABLE_LP_MAIN);
14520
14521
        $sql = "SELECT iid
14522
                FROM $table
14523
            WHERE c_id = $courseId AND next_lp_id = $nextLpId";
14524
        $rs = Database::query($sql);
14525
        $prevLpId = Database::result($rs, 0, 0);
14526
14527
        return $prevLpId;
14528
    }
14529
14530
    /**
14531
     * Save the next lp id.
14532
     *
14533
     * @param $lpId
14534
     * @param $nextLpId
14535
     */
14536
    public static function saveTheNextLp($lpId, $nextLpId)
14537
    {
14538
        $nextLpId = (int) $nextLpId;
14539
        $table = Database::get_course_table(TABLE_LP_MAIN);
14540
        Database::query("UPDATE $table SET next_lp_id = $nextLpId WHERE iid = $lpId");
14541
    }
14542
14543
    /**
14544
     * @param int $lpId
14545
     *
14546
     * @return string
14547
     */
14548
    public static function getSelectedIcon($lpId)
14549
    {
14550
        $extraFieldValue = new ExtraFieldValue('lp');
14551
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
14552
        $icon = '';
14553
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
14554
            $icon = $lpIcon['value'];
14555
        }
14556
14557
        return $icon;
14558
    }
14559
14560
    /**
14561
     * @param int $lpId
14562
     *
14563
     * @return string
14564
     */
14565
    public static function getSelectedIconHtml($lpId)
14566
    {
14567
        $icon = self::getSelectedIcon($lpId);
14568
14569
        if (empty($icon)) {
14570
            return '';
14571
        }
14572
14573
        $theme = api_get_visual_theme();
14574
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
14575
14576
        return Display::img($path);
14577
    }
14578
14579
    /**
14580
     * @param string $value
14581
     *
14582
     * @return string
14583
     */
14584
    public function cleanItemTitle($value)
14585
    {
14586
        $value = Security::remove_XSS(strip_tags($value));
14587
14588
        return $value;
14589
    }
14590
14591
    public function setItemTitle(FormValidator $form)
14592
    {
14593
        if (api_get_configuration_value('save_titles_as_html')) {
14594
            $form->addHtmlEditor(
14595
                'title',
14596
                get_lang('Title'),
14597
                true,
14598
                false,
14599
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
14600
            );
14601
        } else {
14602
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
14603
            $form->applyFilter('title', 'trim');
14604
            $form->applyFilter('title', 'html_filter');
14605
        }
14606
    }
14607
14608
    /**
14609
     * @return array
14610
     */
14611
    public function getItemsForForm($addParentCondition = false)
14612
    {
14613
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
14614
        $course_id = api_get_course_int_id();
14615
14616
        $sql = "SELECT * FROM $tbl_lp_item
14617
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
14618
14619
        if ($addParentCondition) {
14620
            $sql .= ' AND parent_item_id = 0 ';
14621
        }
14622
        $sql .= ' ORDER BY display_order ASC';
14623
14624
        $result = Database::query($sql);
14625
        $arrLP = [];
14626
        while ($row = Database::fetch_array($result)) {
14627
            $arrLP[] = [
14628
                'iid' => $row['iid'],
14629
                'id' => $row['iid'],
14630
                'item_type' => $row['item_type'],
14631
                'title' => $this->cleanItemTitle($row['title']),
14632
                'title_raw' => $row['title'],
14633
                'path' => $row['path'],
14634
                'description' => Security::remove_XSS($row['description']),
14635
                'parent_item_id' => $row['parent_item_id'],
14636
                'previous_item_id' => $row['previous_item_id'],
14637
                'next_item_id' => $row['next_item_id'],
14638
                'display_order' => $row['display_order'],
14639
                'max_score' => $row['max_score'],
14640
                'min_score' => $row['min_score'],
14641
                'mastery_score' => $row['mastery_score'],
14642
                'prerequisite' => $row['prerequisite'],
14643
                'max_time_allowed' => $row['max_time_allowed'],
14644
                'prerequisite_min_score' => $row['prerequisite_min_score'],
14645
                'prerequisite_max_score' => $row['prerequisite_max_score'],
14646
            ];
14647
        }
14648
14649
        return $arrLP;
14650
    }
14651
14652
    /**
14653
     * Gets whether this SCORM learning path has been marked to use the score
14654
     * as progress. Takes into account whether the learnpath matches (SCORM
14655
     * content + less than 2 items).
14656
     *
14657
     * @return bool True if the score should be used as progress, false otherwise
14658
     */
14659
    public function getUseScoreAsProgress()
14660
    {
14661
        // If not a SCORM, we don't care about the setting
14662
        if ($this->get_type() != 2) {
14663
            return false;
14664
        }
14665
        // If more than one step in the SCORM, we don't care about the setting
14666
        if ($this->get_total_items_count() > 1) {
14667
            return false;
14668
        }
14669
        $extraFieldValue = new ExtraFieldValue('lp');
14670
        $doUseScore = false;
14671
        $useScore = $extraFieldValue->get_values_by_handler_and_field_variable($this->get_id(), 'use_score_as_progress');
14672
        if (!empty($useScore) && isset($useScore['value'])) {
14673
            $doUseScore = $useScore['value'];
14674
        }
14675
14676
        return $doUseScore;
14677
    }
14678
14679
    /**
14680
     * Get the user identifier (user_id or username
14681
     * Depends on scorm_api_username_as_student_id in app/config/configuration.php.
14682
     *
14683
     * @return string User ID or username, depending on configuration setting
14684
     */
14685
    public static function getUserIdentifierForExternalServices()
14686
    {
14687
        if (api_get_configuration_value('scorm_api_username_as_student_id')) {
14688
            return api_get_user_info(api_get_user_id())['username'];
14689
        } elseif (api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id') != null) {
14690
            $extraFieldValue = new ExtraFieldValue('user');
14691
            $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'));
14692
14693
            return $extrafield['value'];
14694
        } else {
14695
            return api_get_user_id();
14696
        }
14697
    }
14698
14699
    /**
14700
     * Save the new order for learning path items.
14701
     *
14702
     * We have to update parent_item_id, previous_item_id, next_item_id, display_order in the database.
14703
     *
14704
     * @param array $orderList A associative array with item ID as key and parent ID as value.
14705
     * @param int   $courseId
14706
     */
14707
    public static function sortItemByOrderList(array $orderList, $courseId = 0)
14708
    {
14709
        $courseId = $courseId ?: api_get_course_int_id();
14710
        $itemList = new LpItemOrderList();
14711
14712
        foreach ($orderList as $id => $parentId) {
14713
            $item = new LpOrderItem($id, $parentId);
14714
            $itemList->add($item);
14715
        }
14716
14717
        $parents = $itemList->getListOfParents();
14718
14719
        foreach ($parents as $parentId) {
14720
            $sameParentLpItemList = $itemList->getItemWithSameParent($parentId);
14721
            $previous_item_id = 0;
14722
            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...
14723
                $item_id = $sameParentLpItemList->list[$i]->id;
14724
                // display_order
14725
                $display_order = $i + 1;
14726
                $itemList->setParametersForId($item_id, $display_order, 'display_order');
14727
                // previous_item_id
14728
                $itemList->setParametersForId($item_id, $previous_item_id, 'previous_item_id');
14729
                $previous_item_id = $item_id;
14730
                // next_item_id
14731
                $next_item_id = 0;
14732
                if ($i < count($sameParentLpItemList->list) - 1) {
14733
                    $next_item_id = $sameParentLpItemList->list[$i + 1]->id;
14734
                }
14735
                $itemList->setParametersForId($item_id, $next_item_id, 'next_item_id');
14736
            }
14737
        }
14738
14739
        $table = Database::get_course_table(TABLE_LP_ITEM);
14740
14741
        foreach ($itemList->list as $item) {
14742
            $params = [];
14743
            $params['display_order'] = $item->display_order;
14744
            $params['previous_item_id'] = $item->previous_item_id;
14745
            $params['next_item_id'] = $item->next_item_id;
14746
            $params['parent_item_id'] = $item->parent_item_id;
14747
14748
            Database::update(
14749
                $table,
14750
                $params,
14751
                [
14752
                    'iid = ? AND c_id = ? ' => [
14753
                        (int) $item->id,
14754
                        (int) $courseId,
14755
                    ],
14756
                ]
14757
            );
14758
        }
14759
    }
14760
14761
    public static function findLastView(
14762
        int $lpId,
14763
        int $studentId,
14764
        int $courseId,
14765
        int $sessionId = 0,
14766
        bool $createIfNotExists = false
14767
    ): array {
14768
        $tblLpView = Database::get_course_table(TABLE_LP_VIEW);
14769
14770
        $sessionCondition = api_get_session_condition($sessionId);
14771
14772
        $sql = "SELECT iid FROM $tblLpView
14773
            WHERE c_id = $courseId AND lp_id = $lpId AND user_id = $studentId $sessionCondition
14774
            ORDER BY view_count DESC";
14775
        $result = Database::query($sql);
14776
14777
        $lpView = Database::fetch_assoc($result);
14778
14779
        if ($createIfNotExists && empty($lpView)) {
14780
            $lpViewId = Database::insert(
14781
                $tblLpView,
14782
                [
14783
                    'c_id' => $courseId,
14784
                    'lp_id' => $lpId,
14785
                    'user_id' => $studentId,
14786
                    'view_count' => 1,
14787
                    'session_id' => $sessionId,
14788
                ]
14789
            );
14790
            Database::update($tblLpView, ['id' => $lpViewId], ['iid = ?' => $lpViewId]);
14791
14792
            return ['iid' => $lpViewId];
14793
        }
14794
14795
        return empty($lpView) ? [] : $lpView;
14796
    }
14797
14798
    /**
14799
     * Check and obtain the lp final item if exist.
14800
     *
14801
     * @return learnpathItem
14802
     */
14803
    public function getFinalItem()
14804
    {
14805
        if (empty($this->items)) {
14806
            return null;
14807
        }
14808
14809
        foreach ($this->items as $item) {
14810
            if ($item->type !== 'final_item') {
14811
                continue;
14812
            }
14813
14814
            return $item;
14815
        }
14816
    }
14817
14818
    /**
14819
     * Get the depth level of LP item.
14820
     *
14821
     * @param array $items
14822
     * @param int   $currentItemId
14823
     *
14824
     * @return int
14825
     */
14826
    private static function get_level_for_item($items, $currentItemId)
14827
    {
14828
        $parentItemId = 0;
14829
        if (isset($items[$currentItemId])) {
14830
            $parentItemId = $items[$currentItemId]->parent;
14831
        }
14832
14833
        if ($parentItemId == 0) {
14834
            return 0;
14835
        } else {
14836
            return self::get_level_for_item($items, $parentItemId) + 1;
14837
        }
14838
    }
14839
14840
    /**
14841
     * Generate the link for a learnpath category as course tool.
14842
     *
14843
     * @param int $categoryId
14844
     *
14845
     * @return string
14846
     */
14847
    private static function getCategoryLinkForTool($categoryId)
14848
    {
14849
        $categoryId = (int) $categoryId;
14850
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
14851
            .http_build_query(
14852
                [
14853
                    'action' => 'view_category',
14854
                    'id' => $categoryId,
14855
                ]
14856
            );
14857
14858
        return $link;
14859
    }
14860
14861
    /**
14862
     * Return the scorm item type object with spaces replaced with _
14863
     * The return result is use to build a css classname like scorm_type_$return.
14864
     *
14865
     * @param $in_type
14866
     *
14867
     * @return mixed
14868
     */
14869
    private static function format_scorm_type_item($in_type)
14870
    {
14871
        return str_replace(' ', '_', $in_type);
14872
    }
14873
14874
    /**
14875
     * Get the LP Final Item Template.
14876
     *
14877
     * @return string
14878
     */
14879
    private function getFinalItemTemplate()
14880
    {
14881
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
14882
    }
14883
14884
    /**
14885
     * Get the LP Final Item Url.
14886
     *
14887
     * @return string
14888
     */
14889
    private function getSavedFinalItem()
14890
    {
14891
        $finalItem = $this->getFinalItem();
14892
        $doc = DocumentManager::get_document_data_by_id(
14893
            $finalItem->path,
14894
            $this->cc
14895
        );
14896
        if ($doc && file_exists($doc['absolute_path'])) {
14897
            return file_get_contents($doc['absolute_path']);
14898
        }
14899
14900
        return '';
14901
    }
14902
14903
    /**
14904
     * Gets the form to evaluate if it exists contains the extra field extra_authorlpitem
14905
     * to establish authors when editing an item of an LP.
14906
     */
14907
    private function setAuthorLpItem(FormValidator $form)
14908
    {
14909
        if ($form->hasElement('extra_authorlpitem')) {
14910
            /** @var HTML_QuickForm_select $author */
14911
            $author = $form->getElement('extra_authorlpitem');
14912
            $options = [];
14913
            $field = new ExtraField('user');
14914
            $authorLp = $field->get_handler_field_info_by_field_variable('authorlp');
14915
            $extraFieldId = isset($authorLp['id']) ? (int) $authorLp['id'] : 0;
14916
            if ($extraFieldId != 0) {
14917
                $extraFieldValueUser = new ExtraFieldValue('user');
14918
                $values = $extraFieldValueUser->get_item_id_from_field_variable_and_field_value(
14919
                    $authorLp['variable'],
14920
                    1,
14921
                    true,
14922
                    false,
14923
                    true
14924
                );
14925
14926
                if (!empty($values)) {
14927
                    foreach ($values as $item) {
14928
                        $teacher = api_get_user_info($item['item_id']);
14929
                        $options[$teacher['id']] = $teacher['complete_name'];
14930
                    }
14931
                }
14932
            }
14933
            $author->setOptions($options);
14934
        }
14935
    }
14936
}
14937