Passed
Push — master ( 2338dd...fbaf94 )
by Julito
09:45
created

learnpath   F

Complexity

Total Complexity 1050

Size/Duplication

Total Lines 8874
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 4220
dl 0
loc 8874
rs 0.8
c 0
b 0
f 0
wmc 1050

183 Methods

Rating   Name   Duplication   Size   Complexity  
A getDropElementHtml() 0 12 2
D display_edit_item() 0 71 20
A display_thread_form() 0 13 1
A displayDocumentForm() 0 44 4
C create_document() 0 104 11
A displayItemMenu() 0 89 5
A display_link_form() 0 22 2
F createReadOutText() 0 121 27
A display_item_form() 0 14 1
A getCurrentBuildingModeURL() 0 11 5
A display_document() 0 25 3
A displayNewSectionForm() 0 19 1
A generate_lp_folder() 0 35 4
A display_quiz_form() 0 12 1
D display_item_prerequisites_form() 0 210 15
C display_move_item() 0 55 12
B get_js_dropdown_array() 0 76 6
F build_action_menu() 0 190 15
A edit_document() 0 12 4
A displayResources() 0 52 2
A get_extension() 0 5 1
A generate_learning_path_folder() 0 16 2
A display_student_publication_form() 0 12 1
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 togglePublish() 0 22 4
A get_total_items_count() 0 3 1
A set_autolaunch() 0 27 2
B getCalculateScore() 0 47 6
F save_last() 0 80 21
A get_first_item_id() 0 8 2
A copy() 0 26 1
A get_update_queue() 0 3 1
A getCategoryFromCourseIntoSelect() 0 18 4
F get_link() 0 316 46
A switch_attempt_mode() 0 16 4
A get_course_int_id() 0 3 1
D prerequisites_match() 0 69 16
A previous() 0 11 1
A createCategory() 0 15 1
A get_exercises() 0 133 4
C fixBlockedLinks() 0 64 11
A open() 0 9 1
B edit_item() 0 52 7
A update_default_scorm_commit() 0 25 4
A getCategorySessionId() 0 18 3
A set_error_msg() 0 9 3
D stop_previous_item() 0 55 18
B set_previous_step_as_prerequisite_for_all_items() 0 48 7
C start_current_item() 0 42 16
A updateCategory() 0 9 2
B set_current_item() 0 33 10
A display_lp_prerequisites_list() 0 33 3
A set_modified_on() 0 10 1
A categoryIsPublished() 0 25 2
A get_last() 0 10 2
A get_type() 0 8 3
A getForum() 0 9 1
A get_progress_bar() 0 9 1
A getExercisesItems() 0 13 3
C getBuildTree() 0 196 11
A set_course_int_id() 0 3 1
A getCountCategories() 0 10 2
A get_type_static() 0 16 3
A moveDownCategory() 0 11 2
A get_theme() 0 7 2
A get_items_status_list() 0 10 2
A select_previous_item_id() 0 22 1
A get_previous_index() 0 16 5
B add_item() 0 117 8
A set_seriousgame_mode() 0 15 3
C isBlockedByPrerequisite() 0 68 13
B get_progress_bar_text() 0 56 11
A get_teacher_toc_buttons() 0 29 4
A get_js_lib() 0 8 2
A getItem() 0 7 3
F first() 0 72 20
A getCategoryId() 0 3 1
A get_items_details_as_js() 0 8 2
B get_links() 0 114 7
A get_progress_bar_mode() 0 7 2
A get_user_id() 0 7 2
B set_terms_by_prefix() 0 68 10
A create_path() 0 14 5
A get_current_item_id() 0 8 2
A set_previous_item() 0 6 2
A save_current() 0 32 6
A getLastInFirstLevel() 0 13 2
A getChapterTypes() 0 4 1
B restart() 0 38 6
B get_iv_interactions_array() 0 54 8
A getSubscribeUsers() 0 3 1
A close() 0 13 2
A set_attempt_mode() 0 32 5
A update_display_order() 0 30 5
A deleteCategory() 0 22 3
A getNameNoTags() 0 3 1
C get_mediaplayer() 0 79 13
A moveUpCategory() 0 11 2
A getProgressFromLpList() 0 32 4
C display_item() 0 78 13
F autocomplete_parents() 0 101 17
B overview() 0 59 8
A get_interactions_count_from_db() 0 16 2
B get_scorm_prereq_string() 0 73 11
C getCalculateStars() 0 79 12
B get_scorm_xml_node() 0 19 7
A has_audio() 0 11 3
B edit_item_prereq() 0 36 7
A get_id() 0 7 2
A getFinalEvaluationItem() 0 12 3
A get_next_item_id() 0 10 3
B get_view() 0 53 7
F is_lp_visible_for_student() 0 102 22
B get_flat_ordered_items_list() 0 72 8
A getProgress() 0 23 2
B verify_document_size() 0 25 8
B showBuildSideBar() 0 243 6
A get_complete_items_count() 0 24 5
A get_objectives_count_from_db() 0 16 2
C get_forums() 0 139 9
B delete_item() 0 48 6
B get_documents() 0 103 2
A getTotalItemsCountWithoutDirs() 0 11 3
A getCurrentAttempt() 0 8 2
A display_forum_form() 0 16 2
A update_scorm_debug() 0 15 3
A toggleCategoryVisibility() 0 19 3
C add_lp() 0 107 11
A update_reinit() 0 16 3
A getListArrayToc() 0 9 1
A get_view_id() 0 7 2
A getLpFromSession() 0 33 5
A toggleCategoryPublish() 0 25 4
B get_navigation_bar() 0 72 9
A getCategories() 0 7 1
B get_next_index() 0 23 7
B get_attempt_mode() 0 21 9
D getPackageType() 0 90 20
A toggleVisibility() 0 18 3
A get_lp_session_id() 0 7 2
A get_common_index_terms_by_prefix() 0 16 3
A return_new_tree() 0 17 2
A get_previous_item_id() 0 5 1
A get_toc() 0 19 2
C categoryIsVisibleForStudent() 0 75 15
B delete() 0 102 8
A clearPrerequisites() 0 14 1
A get_student_publications() 0 48 3
A get_name() 0 7 2
A lpHasForum() 0 26 1
A getStatusCSSClassName() 0 7 2
A delete_children_items() 0 22 4
B save_item() 0 47 9
F __construct() 0 251 41
A getAccumulateScormTime() 0 3 1
A getUserIdentifierForExternalServices() 0 14 3
A getAccumulateWorkTime() 0 3 1
B getFinalItemForm() 0 84 4
A getSelectedIcon() 0 10 3
B sortItemByOrderList() 0 59 9
A getAccumulateWorkTimePrerequisite() 0 11 1
A getSelectedIconHtml() 0 12 2
A getItemsForForm() 0 38 3
C rl_get_resource_name() 0 92 14
A getSavedFinalItem() 0 13 3
A cleanItemTitle() 0 5 1
A getCurrentItemParentNames() 0 13 3
A getAccumulateWorkTimeTotalCourse() 0 10 1
B isFirstOrLastItem() 0 32 6
A setItemTitle() 0 14 2
A getCategoryLinkForTool() 0 8 1
A getSubscriptionSettings() 0 14 2
A getUseScoreAsProgress() 0 21 5
A getFinalItem() 0 12 4
A getFinalItemTemplate() 0 3 1
F rl_get_resource_link_for_learnpath() 0 160 31
A getAccumulateWorkTimeTotal() 0 11 1
A getIconSelect() 0 19 4
F exportToCourseBuildFormat() 0 173 31
A get_level_for_item() 0 12 3

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\Course;
6
use Chamilo\CoreBundle\Entity\User;
7
use Chamilo\CoreBundle\Entity\Session as SessionEntity;
8
use Chamilo\CoreBundle\Framework\Container;
9
use Chamilo\CoreBundle\Repository\Node\CourseRepository;
10
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
11
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
12
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
13
use Chamilo\CourseBundle\Entity\CDocument;
14
use Chamilo\CourseBundle\Entity\CForumCategory;
15
use Chamilo\CourseBundle\Entity\CForumThread;
16
use Chamilo\CourseBundle\Entity\CLink;
17
use Chamilo\CourseBundle\Entity\CLp;
18
use Chamilo\CourseBundle\Entity\CLpCategory;
19
use Chamilo\CourseBundle\Entity\CLpItem;
20
use Chamilo\CourseBundle\Entity\CLpItemView;
21
use Chamilo\CourseBundle\Entity\CQuiz;
22
use Chamilo\CourseBundle\Entity\CStudentPublication;
23
use Chamilo\CourseBundle\Entity\CTool;
24
use ChamiloSession as Session;
25
use Doctrine\Common\Collections\Criteria;
26
use PhpZip\ZipFile;
27
use Symfony\Component\Finder\Finder;
28
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
29
30
/**
31
 * Class learnpath
32
 * This class defines the parent attributes and methods for Chamilo learnpaths
33
 * and SCORM learnpaths. It is used by the scorm class.
34
 *
35
 * @todo decouple class
36
 *
37
 * @author  Yannick Warnier <[email protected]>
38
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
39
 */
40
class learnpath
41
{
42
    public const MAX_LP_ITEM_TITLE_LENGTH = 36;
43
    public const STATUS_CSS_CLASS_NAME = [
44
        'not attempted' => 'scorm_not_attempted',
45
        'incomplete' => 'scorm_not_attempted',
46
        'failed' => 'scorm_failed',
47
        'completed' => 'scorm_completed',
48
        'passed' => 'scorm_completed',
49
        'succeeded' => 'scorm_completed',
50
        'browsed' => 'scorm_completed',
51
    ];
52
53
    public $attempt = 0; // The number for the current ID view.
54
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
55
    public $current; // Id of the current item the user is viewing.
56
    public $current_score; // The score of the current item.
57
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
58
    public $current_time_stop; // The time the user closed this resource.
59
    public $default_status = 'not attempted';
60
    public $encoding = 'UTF-8';
61
    public $error = '';
62
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
63
    public $index; // The index of the active learnpath_item in $ordered_items array.
64
    /** @var learnpathItem[] */
65
    public $items = [];
66
    public $last; // item_id of last item viewed in the learning path.
67
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
68
    public $license; // Which license this course has been given - not used yet on 20060522.
69
    public $lp_id; // DB iid for this learnpath.
70
    public $lp_view_id; // DB ID for lp_view
71
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
72
    public $message = '';
73
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
74
    public $name; // Learnpath name (they generally have one).
75
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
76
    public $path = ''; // Path inside the scorm directory (if scorm).
77
    public $theme; // The current theme of the learning path.
78
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
79
    public $accumulateWorkTime; // The min time of learnpath
80
81
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
82
    public $prevent_reinit = 1;
83
84
    // Describes the mode of progress bar display.
85
    public $seriousgame_mode = 0;
86
    public $progress_bar_mode = '%';
87
88
    // Percentage progress as saved in the db.
89
    public $progress_db = 0;
90
    public $proximity; // Wether the content is distant or local or unknown.
91
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
92
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
93
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
94
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
95
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
96
    public $user_id; //ID of the user that is viewing/using the course
97
    public $update_queue = [];
98
    public $scorm_debug = 0;
99
    public $arrMenu = []; // Array for the menu items.
100
    public $debug = 0; // Logging level.
101
    public $lp_session_id = 0;
102
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
103
    public $prerequisite = 0;
104
    public $use_max_score = 1; // 1 or 0
105
    public $subscribeUsers = 0; // Subscribe users or not
106
    public $created_on = '';
107
    public $modified_on = '';
108
    public $publicated_on = '';
109
    public $expired_on = '';
110
    public $ref;
111
    public $course_int_id;
112
    public $course_info;
113
    public $categoryId;
114
    public $scormUrl;
115
    public $entity;
116
117
    public function __construct(CLp $entity = null, $course_info, $user_id)
118
    {
119
        $debug = $this->debug;
120
        $user_id = (int) $user_id;
121
        $this->encoding = api_get_system_encoding();
122
        $lp_id = 0;
123
        if (null !== $entity) {
124
            $lp_id = $entity->getIid();
125
        }
126
        $course_info = empty($course_info) ? api_get_course_info() : $course_info;
127
        $course_id = (int) $course_info['real_id'];
128
        $this->course_info = $course_info;
129
        $this->set_course_int_id($course_id);
130
        if (empty($lp_id) || empty($course_id)) {
131
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
132
        } else {
133
            //$this->entity = $entity;
134
            $this->lp_id = $lp_id;
135
            $this->type = $entity->getLpType();
136
            $this->name = stripslashes($entity->getName());
137
            $this->proximity = $entity->getContentLocal();
138
            $this->theme = $entity->getTheme();
139
            $this->maker = $entity->getContentLocal();
140
            $this->prevent_reinit = $entity->getPreventReinit();
141
            $this->seriousgame_mode = $entity->getSeriousgameMode();
142
            $this->license = $entity->getContentLicense();
143
            $this->scorm_debug = $entity->getDebug();
144
            $this->js_lib = $entity->getJsLib();
145
            $this->path = $entity->getPath();
146
            $this->author = $entity->getAuthor();
147
            $this->hide_toc_frame = $entity->getHideTocFrame();
148
            //$this->lp_session_id = $entity->getSessionId();
149
            $this->use_max_score = $entity->getUseMaxScore();
150
            $this->subscribeUsers = $entity->getSubscribeUsers();
151
            $this->created_on = $entity->getCreatedOn()->format('Y-m-d H:i:s');
152
            $this->modified_on = $entity->getModifiedOn()->format('Y-m-d H:i:s');
153
            $this->ref = $entity->getRef();
154
            $this->categoryId = 0;
155
            if ($entity->getCategory()) {
156
                $this->categoryId = $entity->getCategory()->getIid();
157
            }
158
159
            if ($entity->hasAsset()) {
160
                $asset = $entity->getAsset();
161
                $this->scormUrl = Container::getAssetRepository()->getAssetUrl($asset).'/'.$entity->getPath().'/';
162
            }
163
164
            $this->accumulateScormTime = $entity->getAccumulateWorkTime();
165
166
            if (!empty($entity->getPublicatedOn())) {
167
                $this->publicated_on = $entity->getPublicatedOn()->format('Y-m-d H:i:s');
168
            }
169
170
            if (!empty($entity->getExpiredOn())) {
171
                $this->expired_on = $entity->getExpiredOn()->format('Y-m-d H:i:s');
172
            }
173
            if (2 == $this->type) {
174
                if (1 == $entity->getForceCommit()) {
175
                    $this->force_commit = true;
176
                }
177
            }
178
            $this->mode = $entity->getDefaultViewMod();
179
180
            // Check user ID.
181
            if (empty($user_id)) {
182
                $this->error = 'User ID is empty';
183
            } else {
184
                $this->user_id = $user_id;
185
            }
186
187
            // End of variables checking.
188
            $session_id = api_get_session_id();
189
            //  Get the session condition for learning paths of the base + session.
190
            $session = api_get_session_condition($session_id);
191
            // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
192
            $lp_table = Database::get_course_table(TABLE_LP_VIEW);
193
194
            // Selecting by view_count descending allows to get the highest view_count first.
195
            $sql = "SELECT * FROM $lp_table
196
                    WHERE
197
                        c_id = $course_id AND
198
                        lp_id = $lp_id AND
199
                        user_id = $user_id
200
                        $session
201
                    ORDER BY view_count DESC";
202
            $res = Database::query($sql);
203
204
            if (Database::num_rows($res) > 0) {
205
                $row = Database::fetch_array($res);
206
                $this->attempt = $row['view_count'];
207
                $this->lp_view_id = $row['iid'];
208
                $this->last_item_seen = $row['last_item'];
209
                $this->progress_db = $row['progress'];
210
                $this->lp_view_session_id = $row['session_id'];
211
            } elseif (!api_is_invitee()) {
212
                $this->attempt = 1;
213
                $params = [
214
                    'c_id' => $course_id,
215
                    'lp_id' => $lp_id,
216
                    'user_id' => $user_id,
217
                    'view_count' => 1,
218
                    //'session_id' => $session_id,
219
                    'last_item' => 0,
220
                ];
221
                if (!empty($session_id)) {
222
                    $params['session_id'] = $session_id;
223
                }
224
                $this->last_item_seen = 0;
225
                $this->lp_view_session_id = $session_id;
226
                $this->lp_view_id = Database::insert($lp_table, $params);
227
            }
228
229
            $criteria = new Criteria();
230
            $criteria
231
                ->where($criteria->expr()->neq('path', 'root'))
232
                ->orderBy(
233
                    [
234
                        'parent' => Criteria::ASC,
235
                        'displayOrder' => Criteria::ASC,
236
                    ]
237
                );
238
            $items = $entity->getItems()->matching($criteria);
239
            $lp_item_id_list = [];
240
            foreach ($items as $item) {
241
                $itemId = $item->getIid();
242
                $lp_item_id_list[] = $itemId;
243
244
                switch ($this->type) {
245
                    case 3: //aicc
246
                        $oItem = new aiccItem('db', $itemId, $course_id);
247
                        if (is_object($oItem)) {
248
                            $oItem->set_lp_view($this->lp_view_id, $course_id);
249
                            $oItem->set_prevent_reinit($this->prevent_reinit);
250
                            // Don't use reference here as the next loop will make the pointed object change.
251
                            $this->items[$itemId] = $oItem;
252
                            $this->refs_list[$oItem->ref] = $itemId;
253
                        }
254
                        break;
255
                    case 2:
256
                        $oItem = new scormItem('db', $itemId, $course_id);
257
                        if (is_object($oItem)) {
258
                            $oItem->set_lp_view($this->lp_view_id, $course_id);
259
                            $oItem->set_prevent_reinit($this->prevent_reinit);
260
                            // Don't use reference here as the next loop will make the pointed object change.
261
                            $this->items[$itemId] = $oItem;
262
                            $this->refs_list[$oItem->ref] = $itemId;
263
                        }
264
                        break;
265
                    case 1:
266
                    default:
267
                        $oItem = new learnpathItem(null, $item);
268
                        if (is_object($oItem)) {
269
                            // Moved down to when we are sure the item_view exists.
270
                            //$oItem->set_lp_view($this->lp_view_id);
271
                            $oItem->set_prevent_reinit($this->prevent_reinit);
272
                            // Don't use reference here as the next loop will make the pointed object change.
273
                            $this->items[$itemId] = $oItem;
274
                            $this->refs_list[$itemId] = $itemId;
275
                        }
276
                        break;
277
                }
278
279
                // Setting the object level with variable $this->items[$i][parent]
280
                foreach ($this->items as $itemLPObject) {
281
                    $level = self::get_level_for_item($this->items, $itemLPObject->db_id);
282
                    $itemLPObject->level = $level;
283
                }
284
285
                // Setting the view in the item object.
286
                if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
287
                    $this->items[$itemId]->set_lp_view($this->lp_view_id, $course_id);
288
                    if (TOOL_HOTPOTATOES == $this->items[$itemId]->get_type()) {
289
                        $this->items[$itemId]->current_start_time = 0;
290
                        $this->items[$itemId]->current_stop_time = 0;
291
                    }
292
                }
293
            }
294
295
            if (!empty($lp_item_id_list)) {
296
                $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
297
                if (!empty($lp_item_id_list_to_string)) {
298
                    // Get last viewing vars.
299
                    $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
300
                    // This query should only return one or zero result.
301
                    $sql = "SELECT lp_item_id, status
302
                            FROM $itemViewTable
303
                            WHERE
304
                                lp_view_id = ".$this->get_view_id()." AND
305
                                lp_item_id IN ('".$lp_item_id_list_to_string."')
306
                            ORDER BY view_count DESC ";
307
                    $status_list = [];
308
                    $res = Database::query($sql);
309
                    while ($row = Database:: fetch_array($res)) {
310
                        $status_list[$row['lp_item_id']] = $row['status'];
311
                    }
312
                    foreach ($lp_item_id_list as $item_id) {
313
                        if (isset($status_list[$item_id])) {
314
                            $status = $status_list[$item_id];
315
                            if (is_object($this->items[$item_id])) {
316
                                $this->items[$item_id]->set_status($status);
317
                                if (empty($status)) {
318
                                    $this->items[$item_id]->set_status(
319
                                        $this->default_status
320
                                    );
321
                                }
322
                            }
323
                        } else {
324
                            if (!api_is_invitee()) {
325
                                if (isset($this->items[$item_id]) && is_object($this->items[$item_id])) {
326
                                    $this->items[$item_id]->set_status(
327
                                        $this->default_status
328
                                    );
329
                                }
330
331
                                if (!empty($this->lp_view_id)) {
332
                                    // Add that row to the lp_item_view table so that
333
                                    // we have something to show in the stats page.
334
                                    $params = [
335
                                        'lp_item_id' => $item_id,
336
                                        'lp_view_id' => $this->lp_view_id,
337
                                        'view_count' => 1,
338
                                        'status' => 'not attempted',
339
                                        'start_time' => time(),
340
                                        'total_time' => 0,
341
                                        'score' => 0,
342
                                    ];
343
                                    Database::insert($itemViewTable, $params);
344
345
                                    $this->items[$item_id]->set_lp_view(
346
                                        $this->lp_view_id,
347
                                        $course_id
348
                                    );
349
                                }
350
                            }
351
                        }
352
                    }
353
                }
354
            }
355
356
            $this->ordered_items = self::get_flat_ordered_items_list($entity, null);
357
            $this->max_ordered_items = 0;
358
            foreach ($this->ordered_items as $index => $dummy) {
359
                if ($index > $this->max_ordered_items && !empty($dummy)) {
360
                    $this->max_ordered_items = $index;
361
                }
362
            }
363
            // TODO: Define the current item better.
364
            $this->first();
365
            if ($debug) {
366
                error_log('lp_view_session_id '.$this->lp_view_session_id);
367
                error_log('End of learnpath constructor for learnpath '.$this->get_id());
368
            }
369
        }
370
    }
371
372
    /**
373
     * @return int
374
     */
375
    public function get_course_int_id()
376
    {
377
        return $this->course_int_id ?? api_get_course_int_id();
378
    }
379
380
    /**
381
     * @param $course_id
382
     *
383
     * @return int
384
     */
385
    public function set_course_int_id($course_id)
386
    {
387
        return $this->course_int_id = (int) $course_id;
388
    }
389
390
    /**
391
     * Function rewritten based on old_add_item() from Yannick Warnier.
392
     * Due the fact that users can decide where the item should come, I had to overlook this function and
393
     * I found it better to rewrite it. Old function is still available.
394
     * Added also the possibility to add a description.
395
     *
396
     * @param CLpItem $parent
397
     * @param int     $previousId
398
     * @param string  $type
399
     * @param int     $id resource ID (ref)
400
     * @param string  $title
401
     * @param string  $description
402
     * @param int     $prerequisites
403
     * @param int     $max_time_allowed
404
     * @param int     $userId
405
     *
406
     * @return int
407
     */
408
    public function add_item(
409
        ?CLpItem $parent,
410
        $previousId,
411
        $type,
412
        $id,
413
        $title,
414
        $description = '',
415
        $prerequisites = 0,
416
        $max_time_allowed = 0
417
    ) {
418
        $type = empty($type) ? 'dir' : $type;
419
        $course_id = $this->course_info['real_id'];
420
        if (empty($course_id)) {
421
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
422
            $this->course_info = api_get_course_info($this->cc);
423
            $course_id = $this->course_info['real_id'];
424
        }
425
        $id = (int) $id;
426
        $max_time_allowed = (int) $max_time_allowed;
427
        if (empty($max_time_allowed)) {
428
            $max_time_allowed = 0;
429
        }
430
        $max_score = 100;
431
        if ('quiz' === $type && $id) {
432
            /*$sql = 'SELECT SUM(ponderation)
433
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as q
434
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
435
                    ON q.iid = quiz_rel_question.question_id
436
                    WHERE
437
                        quiz_rel_question.quiz_id = '.$id;
438
            $rsQuiz = Database::query($sql);
439
            $max_score = Database::result($rsQuiz, 0, 0);*/
440
441
            // Disabling the exercise if we add it inside a LP
442
            $exercise = new Exercise($course_id);
443
            $exercise->read($id);
444
            $max_score = $exercise->get_max_score();
445
446
            $exercise->disable();
447
            $exercise->save();
448
            $title = $exercise->get_formated_title();
449
        }
450
451
        $lpItem = (new CLpItem())
452
            ->setTitle($title)
453
            ->setDescription($description)
454
            ->setPath($id)
455
            ->setLp(api_get_lp_entity($this->get_id()))
456
            ->setItemType($type)
457
            ->setMaxScore($max_score)
458
            ->setMaxTimeAllowed($max_time_allowed)
459
            ->setPrerequisite($prerequisites)
460
            //->setDisplayOrder($display_order + 1)
461
            //->setNextItemId((int) $next)
462
            //->setPreviousItemId($previous)
463
        ;
464
465
        if (!empty($parent))  {
466
            $lpItem->setParent($parent);
467
        }
468
        $em = Database::getManager();
469
        $em->persist($lpItem);
470
        $em->flush();
471
472
        $new_item_id = $lpItem->getIid();
473
        if ($new_item_id) {
474
            // @todo fix upload audio.
475
            // Upload audio.
476
            /*if (!empty($_FILES['mp3']['name'])) {
477
                // Create the audio folder if it does not exist yet.
478
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
479
                if (!is_dir($filepath.'audio')) {
480
                    mkdir(
481
                        $filepath.'audio',
482
                        api_get_permissions_for_new_directories()
483
                    );
484
                    DocumentManager::addDocument(
485
                        $_course,
486
                        '/audio',
487
                        'folder',
488
                        0,
489
                        'audio',
490
                        '',
491
                        0,
492
                        true,
493
                        null,
494
                        $sessionId,
495
                        $userId
496
                    );
497
                }
498
499
                $file_path = handle_uploaded_document(
500
                    $_course,
501
                    $_FILES['mp3'],
502
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
503
                    '/audio',
504
                    $userId,
505
                    '',
506
                    '',
507
                    '',
508
                    '',
509
                    false
510
                );
511
512
                // Getting the filename only.
513
                $file_components = explode('/', $file_path);
514
                $file = $file_components[count($file_components) - 1];
515
516
                // Store the mp3 file in the lp_item table.
517
                $sql = "UPDATE $tbl_lp_item SET
518
                          audio = '".Database::escape_string($file)."'
519
                        WHERE iid = '".intval($new_item_id)."'";
520
                Database::query($sql);
521
            }*/
522
        }
523
524
        return $new_item_id;
525
    }
526
527
    /**
528
     * Static admin function allowing addition of a learnpath to a course.
529
     *
530
     * @param string $courseCode
531
     * @param string $name
532
     * @param string $description
533
     * @param string $learnpath
534
     * @param string $origin
535
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
536
     * @param string $publicated_on
537
     * @param string $expired_on
538
     * @param int    $categoryId
539
     * @param int    $userId
540
     *
541
     * @return CLp
542
     */
543
    public static function add_lp(
544
        $courseCode,
545
        $name,
546
        $description = '',
547
        $learnpath = 'guess',
548
        $origin = 'zip',
549
        $zipname = '',
550
        $publicated_on = '',
551
        $expired_on = '',
552
        $categoryId = 0,
553
        $userId = 0
554
    ) {
555
        global $charset;
556
557
        if (!empty($courseCode)) {
558
            $courseInfo = api_get_course_info($courseCode);
559
            $course_id = $courseInfo['real_id'];
560
        } else {
561
            $course_id = api_get_course_int_id();
562
            $courseInfo = api_get_course_info();
563
        }
564
565
        $categoryId = (int) $categoryId;
566
567
        if (empty($publicated_on)) {
568
            $publicated_on = null;
569
        } else {
570
            $publicated_on = api_get_utc_datetime($publicated_on, true, true);
571
        }
572
573
        if (empty($expired_on)) {
574
            $expired_on = null;
575
        } else {
576
            $expired_on = api_get_utc_datetime($expired_on, true, true);
577
        }
578
579
        /*$check_name = "SELECT * FROM $tbl_lp
580
                       WHERE c_id = $course_id AND name = '".Database::escape_string($name)."'";
581
        $res_name = Database::query($check_name);
582
583
        while (Database::num_rows($res_name)) {
584
            // There is already one such name, update the current one a bit.
585
            $i++;
586
            $name = $name.' - '.$i;
587
            $check_name = "SELECT * FROM $tbl_lp
588
                           WHERE c_id = $course_id AND name = '".Database::escape_string($name)."' ";
589
            $res_name = Database::query($check_name);
590
        }*/
591
        // New name does not exist yet; keep it.
592
        // Escape description.
593
        // Kevin: added htmlentities().
594
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
595
        $type = 1;
596
        switch ($learnpath) {
597
            case 'guess':
598
            case 'aicc':
599
                break;
600
            case 'dokeos':
601
            case 'chamilo':
602
                $type = 1;
603
                break;
604
        }
605
606
        $sessionEntity = api_get_session_entity();
607
        $courseEntity = api_get_course_entity($courseInfo['real_id']);
608
        $lp = null;
609
        switch ($origin) {
610
            case 'zip':
611
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
612
                break;
613
            case 'manual':
614
            default:
615
                /*$get_max = "SELECT MAX(display_order)
616
                            FROM $tbl_lp WHERE c_id = $course_id";
617
                $res_max = Database::query($get_max);
618
                if (Database::num_rows($res_max) < 1) {
619
                    $dsp = 1;
620
                } else {
621
                    $row = Database::fetch_array($res_max);
622
                    $dsp = $row[0] + 1;
623
                }*/
624
625
                $dsp = 1;
626
                $category = null;
627
                if (!empty($categoryId)) {
628
                    $category = Container::getLpCategoryRepository()->find($categoryId);
629
                }
630
631
                $lpRepo = Container::getLpRepository();
632
633
                $lp = (new CLp())
634
                    ->setLpType($type)
635
                    ->setName($name)
636
                    ->setDescription($description)
637
                    ->setDisplayOrder($dsp)
638
                    ->setCategory($category)
639
                    ->setPublicatedOn($publicated_on)
640
                    ->setExpiredOn($expired_on)
641
                    ->setParent($courseEntity)
642
                    ->addCourseLink($courseEntity, $sessionEntity)
643
                ;
644
                $lpRepo->createLp($lp);
645
646
                break;
647
        }
648
649
        return $lp;
650
    }
651
652
    /**
653
     * Auto completes the parents of an item in case it's been completed or passed.
654
     *
655
     * @param int $item Optional ID of the item from which to look for parents
656
     */
657
    public function autocomplete_parents($item)
658
    {
659
        $debug = $this->debug;
660
661
        if (empty($item)) {
662
            $item = $this->current;
663
        }
664
665
        $currentItem = $this->getItem($item);
666
        if ($currentItem) {
667
            $parent_id = $currentItem->get_parent();
668
            $parent = $this->getItem($parent_id);
669
            if ($parent) {
670
                // if $item points to an object and there is a parent.
671
                if ($debug) {
672
                    error_log(
673
                        'Autocompleting parent of item '.$item.' '.
674
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
675
                        0
676
                    );
677
                }
678
679
                // New experiment including failed and browsed in completed status.
680
                //$current_status = $currentItem->get_status();
681
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
682
                // Fixes chapter auto complete
683
                if (true) {
684
                    // If the current item is completed or passes or succeeded.
685
                    $updateParentStatus = true;
686
                    if ($debug) {
687
                        error_log('Status of current item is alright');
688
                    }
689
690
                    foreach ($parent->get_children() as $childItemId) {
691
                        $childItem = $this->getItem($childItemId);
692
693
                        // If children was not set try to get the info
694
                        if (empty($childItem->db_item_view_id)) {
695
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
696
                        }
697
698
                        // Check all his brothers (parent's children) for completion status.
699
                        if ($childItemId != $item) {
700
                            if ($debug) {
701
                                error_log(
702
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
703
                                    0
704
                                );
705
                            }
706
                            // Trying completing parents of failed and browsed items as well.
707
                            if ($childItem->status_is(
708
                                [
709
                                    'completed',
710
                                    'passed',
711
                                    'succeeded',
712
                                    'browsed',
713
                                    'failed',
714
                                ]
715
                            )
716
                            ) {
717
                                // Keep completion status to true.
718
                                continue;
719
                            } else {
720
                                if ($debug > 2) {
721
                                    error_log(
722
                                        '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,
723
                                        0
724
                                    );
725
                                }
726
                                $updateParentStatus = false;
727
                                break;
728
                            }
729
                        }
730
                    }
731
732
                    if ($updateParentStatus) {
733
                        // If all the children were completed:
734
                        $parent->set_status('completed');
735
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
736
                        // Force the status to "completed"
737
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
738
                        $this->update_queue[$parent->get_id()] = 'completed';
739
                        if ($debug) {
740
                            error_log(
741
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
742
                                print_r($this->update_queue, 1),
743
                                0
744
                            );
745
                        }
746
                        // Recursive call.
747
                        $this->autocomplete_parents($parent->get_id());
748
                    }
749
                }
750
            } else {
751
                if ($debug) {
752
                    error_log("Parent #$parent_id does not exists");
753
                }
754
            }
755
        } else {
756
            if ($debug) {
757
                error_log("#$item is an item that doesn't have parents");
758
            }
759
        }
760
    }
761
762
    /**
763
     * Closes the current resource.
764
     *
765
     * Stops the timer
766
     * Saves into the database if required
767
     * Clears the current resource data from this object
768
     *
769
     * @return bool True on success, false on failure
770
     */
771
    public function close()
772
    {
773
        if (empty($this->lp_id)) {
774
            $this->error = 'Trying to close this learnpath but no ID is set';
775
776
            return false;
777
        }
778
        $this->current_time_stop = time();
779
        $this->ordered_items = [];
780
        $this->index = 0;
781
        unset($this->lp_id);
782
        //unset other stuff
783
        return true;
784
    }
785
786
    /**
787
     * Static admin function allowing removal of a learnpath.
788
     *
789
     * @param array  $courseInfo
790
     * @param int    $id         Learnpath ID
791
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
792
     *
793
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
794
     */
795
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
796
    {
797
        $course_id = api_get_course_int_id();
798
        if (!empty($courseInfo)) {
799
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
800
        }
801
802
        // TODO: Implement a way of getting this to work when the current object is not set.
803
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
804
        // If an ID is specifically given and the current LP is not the same, prevent delete.
805
        if (!empty($id) && ($id != $this->lp_id)) {
806
            return false;
807
        }
808
809
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
810
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
811
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
812
813
        // Delete lp item id.
814
        foreach ($this->items as $lpItemId => $dummy) {
815
            $sql = "DELETE FROM $lp_item_view
816
                    WHERE lp_item_id = '".$lpItemId."'";
817
            Database::query($sql);
818
        }
819
820
        // Proposed by Christophe (nickname: clefevre)
821
        $sql = "DELETE FROM $lp_item
822
                WHERE lp_id = ".$this->lp_id;
823
        Database::query($sql);
824
825
        $sql = "DELETE FROM $lp_view
826
                WHERE lp_id = ".$this->lp_id;
827
        Database::query($sql);
828
829
        //self::toggleVisibility($this->lp_id, 0);
830
831
        /*if (2 == $this->type || 3 == $this->type) {
832
            // This is a scorm learning path, delete the files as well.
833
            $sql = "SELECT path FROM $lp
834
                    WHERE iid = ".$this->lp_id;
835
            $res = Database::query($sql);
836
            if (Database::num_rows($res) > 0) {
837
                $row = Database::fetch_array($res);
838
                $path = $row['path'];
839
                $sql = "SELECT iid FROM $lp
840
                        WHERE
841
                            c_id = $course_id AND
842
                            path = '$path' AND
843
                            iid != ".$this->lp_id;
844
                $res = Database::query($sql);
845
                if (Database::num_rows($res) > 0) {
846
                    // Another learning path uses this directory, so don't delete it.
847
                    if ($this->debug > 2) {
848
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
849
                    }
850
                } else {
851
                    // No other LP uses that directory, delete it.
852
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
853
                    // The absolute system path for this course.
854
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
855
                    if ('remove' == $delete && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
856
                        if ($this->debug > 2) {
857
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
858
                        }
859
                        // Proposed by Christophe (clefevre).
860
                        if (0 == strcmp(substr($path, -2), "/.")) {
861
                            $path = substr($path, 0, -1); // Remove "." at the end.
862
                        }
863
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
864
                        rmdirr($course_scorm_dir.$path);
865
                    }
866
                }
867
            }
868
        }*/
869
870
        $table = Database::get_course_table(TABLE_LP_REL_USERGROUP);
871
        $sql = "DELETE FROM $table
872
                WHERE
873
                    lp_id = {$this->lp_id}";
874
        Database::query($sql);
875
876
        $repo = Container::getLpRepository();
877
        $lp = $repo->find($this->lp_id);
878
        Database::getManager()->remove($lp);
879
        Database::getManager()->flush();
880
881
        // Updates the display order of all lps.
882
        $this->update_display_order();
883
884
        $link_info = GradebookUtils::isResourceInCourseGradebook(
885
            api_get_course_id(),
886
            4,
887
            $id,
888
            api_get_session_id()
889
        );
890
891
        if (false !== $link_info) {
892
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
893
        }
894
895
        if ('true' === api_get_setting('search_enabled')) {
896
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
897
        }
898
    }
899
900
    /**
901
     * Removes all the children of one item - dangerous!
902
     *
903
     * @param int $id Element ID of which children have to be removed
904
     *
905
     * @return int Total number of children removed
906
     */
907
    public function delete_children_items($id)
908
    {
909
        $course_id = $this->course_info['real_id'];
910
911
        $num = 0;
912
        $id = (int) $id;
913
        if (empty($id) || empty($course_id)) {
914
            return false;
915
        }
916
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
917
        $sql = "SELECT * FROM $lp_item
918
                WHERE parent_item_id = $id";
919
        $res = Database::query($sql);
920
        while ($row = Database::fetch_array($res)) {
921
            $num += $this->delete_children_items($row['iid']);
922
            $sql = "DELETE FROM $lp_item
923
                    WHERE iid = ".$row['iid'];
924
            Database::query($sql);
925
            $num++;
926
        }
927
928
        return $num;
929
    }
930
931
    /**
932
     * Removes an item from the current learnpath.
933
     *
934
     * @param int $id Elem ID (0 if first)
935
     *
936
     * @return int Number of elements moved
937
     *
938
     * @todo implement resource removal
939
     */
940
    public function delete_item($id)
941
    {
942
        $course_id = api_get_course_int_id();
943
        $id = (int) $id;
944
        // TODO: Implement the resource removal.
945
        if (empty($id) || empty($course_id)) {
946
            return false;
947
        }
948
949
        $repo = Container::getLpItemRepository();
950
        $item = $repo->find($id);
951
        if (null === $item) {
952
            return false;
953
        }
954
955
        $em = Database::getManager();
956
        $repo->removeFromTree($item);
957
        $em->flush();
958
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
959
960
        //Removing prerequisites since the item will not longer exist
961
        $sql_all = "UPDATE $lp_item SET prerequisite = ''
962
                    WHERE prerequisite = '$id'";
963
        Database::query($sql_all);
964
965
        $sql = "UPDATE $lp_item
966
                SET previous_item_id = ".$this->getLastInFirstLevel()."
967
                WHERE lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
968
        Database::query($sql);
969
970
        // Remove from search engine if enabled.
971
        if ('true' === api_get_setting('search_enabled')) {
972
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
973
            $sql = 'SELECT * FROM %s
974
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
975
                    LIMIT 1';
976
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
977
            $res = Database::query($sql);
978
            if (Database::num_rows($res) > 0) {
979
                $row2 = Database::fetch_array($res);
980
                $di = new ChamiloIndexer();
981
                $di->remove_document($row2['search_did']);
982
            }
983
            $sql = 'DELETE FROM %s
984
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
985
                    LIMIT 1';
986
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
987
            Database::query($sql);
988
        }
989
    }
990
991
    /**
992
     * Updates an item's content in place.
993
     *
994
     * @param int    $id               Element ID
995
     * @param int    $parent           Parent item ID
996
     * @param int    $previous         Previous item ID
997
     * @param string $title            Item title
998
     * @param string $description      Item description
999
     * @param string $prerequisites    Prerequisites (optional)
1000
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1001
     * @param int    $max_time_allowed
1002
     * @param string $url
1003
     *
1004
     * @return bool True on success, false on error
1005
     */
1006
    public function edit_item(
1007
        $id,
1008
        $parent,
1009
        $previous,
1010
        $title,
1011
        $description,
1012
        $prerequisites = '0',
1013
        $audio = [],
1014
        $max_time_allowed = 0,
1015
        $url = ''
1016
    ) {
1017
        $_course = api_get_course_info();
1018
        $id = (int) $id;
1019
1020
        if (empty($id) || empty($_course)) {
1021
            return false;
1022
        }
1023
        $repo = Container::getLpItemRepository();
1024
        /** @var CLpItem $item */
1025
        $item = $repo->find($id);
1026
        if (null === $item) {
1027
            return false;
1028
        }
1029
1030
        $item
1031
            ->setTitle($title)
1032
            ->setDescription($description)
1033
            ->setPrerequisite($prerequisites)
1034
            ->setMaxTimeAllowed((int) $max_time_allowed)
1035
        ;
1036
1037
        $em = Database::getManager();
1038
        if (!empty($parent)) {
1039
            $parent = $repo->find($parent);
1040
            $item->setParent($parent);
1041
        } else {
1042
            $item->setParent(null);
1043
        }
1044
1045
        if (!empty($previous)) {
1046
            $previous = $repo->find($previous);
1047
            $repo->persistAsNextSiblingOf( $item, $previous);
1048
        } else {
1049
            $em->persist($item);
1050
        }
1051
1052
        $em->flush();
1053
1054
        if ('link' === $item->getItemType()) {
1055
            $link = new Link();
1056
            $linkId = $item->getPath();
1057
            $link->updateLink($linkId, $url);
1058
        }
1059
    }
1060
1061
    /**
1062
     * Updates an item's prereq in place.
1063
     *
1064
     * @param int    $id              Element ID
1065
     * @param string $prerequisite_id Prerequisite Element ID
1066
     * @param int    $minScore        Prerequisite min score
1067
     * @param int    $maxScore        Prerequisite max score
1068
     *
1069
     * @return bool True on success, false on error
1070
     */
1071
    public function edit_item_prereq($id, $prerequisite_id, $minScore = 0, $maxScore = 100)
1072
    {
1073
        $id = (int) $id;
1074
1075
        if (empty($id)) {
1076
            return false;
1077
        }
1078
        $prerequisite_id = (int) $prerequisite_id;
1079
1080
        if (empty($minScore) || $minScore < 0) {
1081
            $minScore = 0;
1082
        }
1083
1084
        if (empty($maxScore) || $maxScore < 0) {
1085
            $maxScore = 100;
1086
        }
1087
1088
        $minScore = (float) $minScore;
1089
        $maxScore = (float) $maxScore;
1090
1091
        if (empty($prerequisite_id)) {
1092
            $prerequisite_id = 'NULL';
1093
            $minScore = 0;
1094
            $maxScore = 100;
1095
        }
1096
1097
        $table = Database::get_course_table(TABLE_LP_ITEM);
1098
        $sql = " UPDATE $table
1099
                 SET
1100
                    prerequisite = $prerequisite_id ,
1101
                    prerequisite_min_score = $minScore ,
1102
                    prerequisite_max_score = $maxScore
1103
                 WHERE iid = $id";
1104
        Database::query($sql);
1105
1106
        return true;
1107
    }
1108
1109
    /**
1110
     * Get the specific prefix index terms of this learning path.
1111
     *
1112
     * @param string $prefix
1113
     *
1114
     * @return array Array of terms
1115
     */
1116
    public function get_common_index_terms_by_prefix($prefix)
1117
    {
1118
        $terms = get_specific_field_values_list_by_prefix(
1119
            $prefix,
1120
            $this->cc,
1121
            TOOL_LEARNPATH,
1122
            $this->lp_id
1123
        );
1124
        $prefix_terms = [];
1125
        if (!empty($terms)) {
1126
            foreach ($terms as $term) {
1127
                $prefix_terms[] = $term['value'];
1128
            }
1129
        }
1130
1131
        return $prefix_terms;
1132
    }
1133
1134
    /**
1135
     * Gets the number of items currently completed.
1136
     *
1137
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1138
     *
1139
     * @return int The number of items currently completed
1140
     */
1141
    public function get_complete_items_count($failedStatusException = false)
1142
    {
1143
        $i = 0;
1144
        $completedStatusList = [
1145
            'completed',
1146
            'passed',
1147
            'succeeded',
1148
            'browsed',
1149
        ];
1150
1151
        if (!$failedStatusException) {
1152
            $completedStatusList[] = 'failed';
1153
        }
1154
1155
        foreach ($this->items as $id => $dummy) {
1156
            // Trying failed and browsed considered "progressed" as well.
1157
            if ($this->items[$id]->status_is($completedStatusList) &&
1158
                'dir' !== $this->items[$id]->get_type()
1159
            ) {
1160
                $i++;
1161
            }
1162
        }
1163
1164
        return $i;
1165
    }
1166
1167
    /**
1168
     * Gets the current item ID.
1169
     *
1170
     * @return int The current learnpath item id
1171
     */
1172
    public function get_current_item_id()
1173
    {
1174
        $current = 0;
1175
        if (!empty($this->current)) {
1176
            $current = (int) $this->current;
1177
        }
1178
1179
        return $current;
1180
    }
1181
1182
    /**
1183
     * Force to get the first learnpath item id.
1184
     *
1185
     * @return int The current learnpath item id
1186
     */
1187
    public function get_first_item_id()
1188
    {
1189
        $current = 0;
1190
        if (is_array($this->ordered_items)) {
1191
            $current = $this->ordered_items[0];
1192
        }
1193
1194
        return $current;
1195
    }
1196
1197
    /**
1198
     * Gets the total number of items available for viewing in this SCORM.
1199
     *
1200
     * @return int The total number of items
1201
     */
1202
    public function get_total_items_count()
1203
    {
1204
        return count($this->items);
1205
    }
1206
1207
    /**
1208
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1209
     *
1210
     * @return int The total no-chapters number of items
1211
     */
1212
    public function getTotalItemsCountWithoutDirs()
1213
    {
1214
        $total = 0;
1215
        $typeListNotToCount = self::getChapterTypes();
1216
        foreach ($this->items as $temp2) {
1217
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1218
                $total++;
1219
            }
1220
        }
1221
1222
        return $total;
1223
    }
1224
1225
    /**
1226
     *  Sets the first element URL.
1227
     */
1228
    public function first()
1229
    {
1230
        if ($this->debug > 0) {
1231
            error_log('In learnpath::first()', 0);
1232
            error_log('$this->last_item_seen '.$this->last_item_seen);
1233
        }
1234
1235
        // Test if the last_item_seen exists and is not a dir.
1236
        if (0 == count($this->ordered_items)) {
1237
            $this->index = 0;
1238
        }
1239
1240
        if (!empty($this->last_item_seen) &&
1241
            !empty($this->items[$this->last_item_seen]) &&
1242
            'dir' !== $this->items[$this->last_item_seen]->get_type()
1243
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1244
            //&& !$this->items[$this->last_item_seen]->is_done()
1245
        ) {
1246
            if ($this->debug > 2) {
1247
                error_log(
1248
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1249
                    $this->items[$this->last_item_seen]->get_type()
1250
                );
1251
            }
1252
            $index = -1;
1253
            foreach ($this->ordered_items as $myindex => $item_id) {
1254
                if ($item_id == $this->last_item_seen) {
1255
                    $index = $myindex;
1256
                    break;
1257
                }
1258
            }
1259
            if (-1 == $index) {
1260
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1261
                if ($this->debug > 2) {
1262
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1263
                }
1264
1265
                return false;
1266
            } else {
1267
                $this->last = $this->last_item_seen;
1268
                $this->current = $this->last_item_seen;
1269
                $this->index = $index;
1270
            }
1271
        } else {
1272
            if ($this->debug > 2) {
1273
                error_log('In learnpath::first() - No last item seen', 0);
1274
            }
1275
            $index = 0;
1276
            // Loop through all ordered items and stop at the first item that is
1277
            // not a directory *and* that has not been completed yet.
1278
            while (!empty($this->ordered_items[$index]) &&
1279
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1280
                (
1281
                    'dir' === $this->items[$this->ordered_items[$index]]->get_type() ||
1282
                    true === $this->items[$this->ordered_items[$index]]->is_done()
1283
                ) && $index < $this->max_ordered_items
1284
            ) {
1285
                $index++;
1286
            }
1287
1288
            $this->last = $this->current;
1289
            // current is
1290
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1291
            $this->index = $index;
1292
            if ($this->debug > 2) {
1293
                error_log('$index '.$index);
1294
                error_log('In learnpath::first() - No last item seen');
1295
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1296
            }
1297
        }
1298
        if ($this->debug > 2) {
1299
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1300
        }
1301
    }
1302
1303
    /**
1304
     * Gets the js library from the database.
1305
     *
1306
     * @return string The name of the javascript library to be used
1307
     */
1308
    public function get_js_lib()
1309
    {
1310
        $lib = '';
1311
        if (!empty($this->js_lib)) {
1312
            $lib = $this->js_lib;
1313
        }
1314
1315
        return $lib;
1316
    }
1317
1318
    /**
1319
     * Gets the learnpath database ID.
1320
     *
1321
     * @return int Learnpath ID in the lp table
1322
     */
1323
    public function get_id()
1324
    {
1325
        if (!empty($this->lp_id)) {
1326
            return (int) $this->lp_id;
1327
        }
1328
1329
        return 0;
1330
    }
1331
1332
    /**
1333
     * Gets the last element URL.
1334
     *
1335
     * @return string URL to load into the viewer
1336
     */
1337
    public function get_last()
1338
    {
1339
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1340
        if (count($this->ordered_items) > 0) {
1341
            $this->index = count($this->ordered_items) - 1;
1342
1343
            return $this->ordered_items[$this->index];
1344
        }
1345
1346
        return false;
1347
    }
1348
1349
    /**
1350
     * Get the last element in the first level.
1351
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1352
     *
1353
     * @return mixed
1354
     */
1355
    public function getLastInFirstLevel()
1356
    {
1357
        try {
1358
            $lastId = Database::getManager()
1359
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1360
                WHERE i.lp = :lp AND i.parent IS NULL AND i.itemType != :type ORDER BY i.displayOrder DESC')
1361
                ->setMaxResults(1)
1362
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1363
                ->getSingleScalarResult();
1364
1365
            return $lastId;
1366
        } catch (Exception $exception) {
1367
            return 0;
1368
        }
1369
    }
1370
1371
    /**
1372
     * Gets the navigation bar for the learnpath display screen.
1373
     *
1374
     * @param string $barId
1375
     *
1376
     * @return string The HTML string to use as a navigation bar
1377
     */
1378
    public function get_navigation_bar($barId = '')
1379
    {
1380
        if (empty($barId)) {
1381
            $barId = 'control-top';
1382
        }
1383
        $lpId = $this->lp_id;
1384
        $mycurrentitemid = $this->get_current_item_id();
1385
        $reportingText = get_lang('Reporting');
1386
        $previousText = get_lang('Previous');
1387
        $nextText = get_lang('Next');
1388
        $fullScreenText = get_lang('Back to normal screen');
1389
1390
        $settings = api_get_configuration_value('lp_view_settings');
1391
        $display = $settings['display'] ?? false;
1392
        $reportingIcon = '
1393
            <a class="icon-toolbar"
1394
                id="stats_link"
1395
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
1396
                onclick="window.parent.API.save_asset(); return true;"
1397
                target="content_name" title="'.$reportingText.'">
1398
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1399
            </a>';
1400
1401
        if (!empty($display)) {
1402
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1403
            if (false === $showReporting) {
1404
                $reportingIcon = '';
1405
            }
1406
        }
1407
1408
        $hideArrows = false;
1409
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1410
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1411
        }
1412
1413
        $previousIcon = '';
1414
        $nextIcon = '';
1415
        if (false === $hideArrows) {
1416
            $previousIcon = '
1417
                <a class="icon-toolbar" id="scorm-previous" href="#"
1418
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1419
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1420
                </a>';
1421
1422
            $nextIcon = '
1423
                <a class="icon-toolbar" id="scorm-next" href="#"
1424
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1425
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1426
                </a>';
1427
        }
1428
1429
        if ('fullscreen' === $this->mode) {
1430
            $navbar = '
1431
                  <span id="'.$barId.'" class="buttons">
1432
                    '.$reportingIcon.'
1433
                    '.$previousIcon.'
1434
                    '.$nextIcon.'
1435
                    <a class="icon-toolbar" id="view-embedded"
1436
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1437
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1438
                    </a>
1439
                  </span>';
1440
        } else {
1441
            $navbar = '
1442
                 <span id="'.$barId.'" class="buttons text-right">
1443
                    '.$reportingIcon.'
1444
                    '.$previousIcon.'
1445
                    '.$nextIcon.'
1446
                </span>';
1447
        }
1448
1449
        return $navbar;
1450
    }
1451
1452
    /**
1453
     * Gets the next resource in queue (url).
1454
     *
1455
     * @return string URL to load into the viewer
1456
     */
1457
    public function get_next_index()
1458
    {
1459
        // TODO
1460
        $index = $this->index;
1461
        $index++;
1462
        while (
1463
            !empty($this->ordered_items[$index]) && ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) &&
1464
            $index < $this->max_ordered_items
1465
        ) {
1466
            $index++;
1467
            if ($index == $this->max_ordered_items) {
1468
                if ('dir' === $this->items[$this->ordered_items[$index]]->get_type()) {
1469
                    return $this->index;
1470
                }
1471
1472
                return $index;
1473
            }
1474
        }
1475
        if (empty($this->ordered_items[$index])) {
1476
            return $this->index;
1477
        }
1478
1479
        return $index;
1480
    }
1481
1482
    /**
1483
     * Gets item_id for the next element.
1484
     *
1485
     * @return int Next item (DB) ID
1486
     */
1487
    public function get_next_item_id()
1488
    {
1489
        $new_index = $this->get_next_index();
1490
        if (!empty($new_index)) {
1491
            if (isset($this->ordered_items[$new_index])) {
1492
                return $this->ordered_items[$new_index];
1493
            }
1494
        }
1495
1496
        return 0;
1497
    }
1498
1499
    /**
1500
     * Returns the package type ('scorm','aicc','scorm2004','ppt'...).
1501
     *
1502
     * Generally, the package provided is in the form of a zip file, so the function
1503
     * has been written to test a zip file. If not a zip, the function will return the
1504
     * default return value: ''
1505
     *
1506
     * @param string $filePath the path to the file
1507
     * @param string $file_name the original name of the file
1508
     *
1509
     * @return string 'scorm','aicc','scorm2004','error-empty-package'
1510
     *                if the package is empty, or '' if the package cannot be recognized
1511
     */
1512
    public static function getPackageType($filePath, $file_name)
1513
    {
1514
        // Get name of the zip file without the extension.
1515
        $file_info = pathinfo($file_name);
1516
        $extension = $file_info['extension']; // Extension only.
1517
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
1518
                'dll',
1519
                'exe',
1520
            ])) {
1521
            return 'oogie';
1522
        }
1523
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
1524
                'dll',
1525
                'exe',
1526
            ])) {
1527
            return 'woogie';
1528
        }
1529
1530
        $zipFile = new ZipFile();
1531
        $zipFile->openFile($filePath);
1532
        $zipContentArray = $zipFile->getEntries();
1533
        $package_type = '';
1534
        $manifest = '';
1535
        $aicc_match_crs = 0;
1536
        $aicc_match_au = 0;
1537
        $aicc_match_des = 0;
1538
        $aicc_match_cst = 0;
1539
        $countItems = 0;
1540
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
1541
        if ($zipContentArray) {
1542
            $countItems = count($zipContentArray);
1543
            if ($countItems > 0) {
1544
                foreach ($zipContentArray as $thisContent) {
1545
                    $fileName = basename($thisContent->getName());
1546
                    if (preg_match('~.(php.*|phtml)$~i', $fileName)) {
1547
                        // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
1548
                    } elseif (false !== stristr($fileName, 'imsmanifest.xml')) {
1549
                        $manifest = $fileName; // Just the relative directory inside scorm/
1550
                        $package_type = 'scorm';
1551
                        break; // Exit the foreach loop.
1552
                    } elseif (
1553
                        preg_match('/aicc\//i', $fileName) ||
1554
                        in_array(
1555
                            strtolower(pathinfo($fileName, PATHINFO_EXTENSION)),
1556
                            ['crs', 'au', 'des', 'cst']
1557
                        )
1558
                    ) {
1559
                        $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
1560
                        switch ($ext) {
1561
                            case 'crs':
1562
                                $aicc_match_crs = 1;
1563
                                break;
1564
                            case 'au':
1565
                                $aicc_match_au = 1;
1566
                                break;
1567
                            case 'des':
1568
                                $aicc_match_des = 1;
1569
                                break;
1570
                            case 'cst':
1571
                                $aicc_match_cst = 1;
1572
                                break;
1573
                            default:
1574
                                break;
1575
                        }
1576
                        //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
1577
                    } else {
1578
                        $package_type = '';
1579
                    }
1580
                }
1581
            }
1582
        }
1583
1584
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
1585
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
1586
            $package_type = 'aicc';
1587
        }
1588
1589
        // Try with chamilo course builder
1590
        if (empty($package_type)) {
1591
            // Sometimes users will try to upload an empty zip, or a zip with
1592
            // only a folder. Catch that and make the calling function aware.
1593
            // If the single file was the imsmanifest.xml, then $package_type
1594
            // would be 'scorm' and we wouldn't be here.
1595
            if ($countItems < 2) {
1596
                return 'error-empty-package';
1597
            }
1598
            $package_type = 'chamilo';
1599
        }
1600
1601
        return $package_type;
1602
    }
1603
1604
    /**
1605
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
1606
     *
1607
     * @return string URL to load into the viewer
1608
     */
1609
    public function get_previous_index()
1610
    {
1611
        $index = $this->index;
1612
        if (isset($this->ordered_items[$index - 1])) {
1613
            $index--;
1614
            while (isset($this->ordered_items[$index]) &&
1615
                ('dir' === $this->items[$this->ordered_items[$index]]->get_type())
1616
            ) {
1617
                $index--;
1618
                if ($index < 0) {
1619
                    return $this->index;
1620
                }
1621
            }
1622
        }
1623
1624
        return $index;
1625
    }
1626
1627
    /**
1628
     * Gets item_id for the next element.
1629
     *
1630
     * @return int Previous item (DB) ID
1631
     */
1632
    public function get_previous_item_id()
1633
    {
1634
        $index = $this->get_previous_index();
1635
1636
        return $this->ordered_items[$index];
1637
    }
1638
1639
    /**
1640
     * Returns the HTML necessary to print a mediaplayer block inside a page.
1641
     *
1642
     * @param int    $lpItemId
1643
     * @param string $autostart
1644
     *
1645
     * @return string The mediaplayer HTML
1646
     */
1647
    public function get_mediaplayer($lpItemId, $autostart = 'true')
1648
    {
1649
        $courseInfo = api_get_course_info();
1650
        $lpItemId = (int) $lpItemId;
1651
1652
        if (empty($courseInfo) || empty($lpItemId)) {
1653
            return '';
1654
        }
1655
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
1656
1657
        if (empty($item)) {
1658
            return '';
1659
        }
1660
1661
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1662
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1663
        $itemViewId = (int) $item->db_item_view_id;
1664
1665
        // Getting all the information about the item.
1666
        $sql = "SELECT lp_view.status
1667
                FROM $tbl_lp_item as lpi
1668
                INNER JOIN $tbl_lp_item_view as lp_view
1669
                ON (lpi.iid = lp_view.lp_item_id)
1670
                WHERE
1671
                    lp_view.iid = $itemViewId AND
1672
                    lpi.iid = $lpItemId
1673
                ";
1674
        $result = Database::query($sql);
1675
        $row = Database::fetch_assoc($result);
1676
        $output = '';
1677
        $audio = $item->audio;
1678
1679
        if (!empty($audio)) {
1680
            $list = $_SESSION['oLP']->get_toc();
1681
1682
            switch ($item->get_type()) {
1683
                case 'quiz':
1684
                    $type_quiz = false;
1685
                    foreach ($list as $toc) {
1686
                        if ($toc['id'] == $_SESSION['oLP']->current) {
1687
                            $type_quiz = true;
1688
                        }
1689
                    }
1690
1691
                    if ($type_quiz) {
1692
                        if (1 == $_SESSION['oLP']->prevent_reinit) {
1693
                            $autostart_audio = 'completed' === $row['status'] ? 'false' : 'true';
1694
                        } else {
1695
                            $autostart_audio = $autostart;
1696
                        }
1697
                    }
1698
                    break;
1699
                case TOOL_READOUT_TEXT:
1700
                    $autostart_audio = 'false';
1701
                    break;
1702
                default:
1703
                    $autostart_audio = 'true';
1704
            }
1705
1706
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'.$audio;
1707
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document'.$audio.'?'.api_get_cidreq();
1708
1709
            $player = Display::getMediaPlayer(
1710
                $file,
1711
                [
1712
                    'id' => 'lp_audio_media_player',
1713
                    'url' => $url,
1714
                    'autoplay' => $autostart_audio,
1715
                    'width' => '100%',
1716
                ]
1717
            );
1718
1719
            // The mp3 player.
1720
            $output = '<div id="container">';
1721
            $output .= $player;
1722
            $output .= '</div>';
1723
        }
1724
1725
        return $output;
1726
    }
1727
1728
    /**
1729
     * @param int    $studentId
1730
     * @param int    $prerequisite
1731
     * @param Course $course
1732
     * @param int    $sessionId
1733
     *
1734
     * @return bool
1735
     */
1736
    public static function isBlockedByPrerequisite(
1737
        $studentId,
1738
        $prerequisite,
1739
        Course $course,
1740
        $sessionId
1741
    ) {
1742
        if (null === $course) {
1743
            return false;
1744
        }
1745
1746
        $courseId = $course->getId();
1747
1748
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
1749
        if ($allow) {
1750
            if (api_is_allowed_to_edit() ||
1751
                api_is_platform_admin(true) ||
1752
                api_is_drh() ||
1753
                api_is_coach($sessionId, $courseId, false)
1754
            ) {
1755
                return false;
1756
            }
1757
        }
1758
1759
        $isBlocked = false;
1760
        if (!empty($prerequisite)) {
1761
            $progress = self::getProgress(
1762
                $prerequisite,
1763
                $studentId,
1764
                $courseId,
1765
                $sessionId
1766
            );
1767
            if ($progress < 100) {
1768
                $isBlocked = true;
1769
            }
1770
1771
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
1772
                // Block if it does not exceed minimum time
1773
                // Minimum time (in minutes) to pass the learning path
1774
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
1775
1776
                if ($accumulateWorkTime > 0) {
1777
                    // Total time in course (sum of times in learning paths from course)
1778
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
1779
1780
                    // Connect with the plugin_licences_course_session table
1781
                    // which indicates what percentage of the time applies
1782
                    // Minimum connection percentage
1783
                    $perc = 100;
1784
                    // Time from the course
1785
                    $tc = $accumulateWorkTimeTotal;
1786
1787
                    // Percentage of the learning paths
1788
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
1789
                    // Minimum time for each learning path
1790
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
1791
1792
                    // Spent time (in seconds) so far in the learning path
1793
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
1794
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
1795
1796
                    if ($lpTime < ($accumulateWorkTime * 60)) {
1797
                        $isBlocked = true;
1798
                    }
1799
                }
1800
            }
1801
        }
1802
1803
        return $isBlocked;
1804
    }
1805
1806
    /**
1807
     * Checks if the learning path is visible for student after the progress
1808
     * of its prerequisite is completed, considering the time availability and
1809
     * the LP visibility.
1810
     */
1811
    public static function is_lp_visible_for_student(CLp $lp, $student_id, Course $course, SessionEntity $session = null): bool
1812
    {
1813
        if (null === $course) {
1814
            return false;
1815
        }
1816
1817
        $sessionId = $session ? $session->getId() : 0;
1818
        $courseId = $course->getId();
1819
        $visibility = $lp->isVisible($course, $session);
1820
1821
        // If the item was deleted.
1822
        if (false === $visibility) {
1823
            return false;
1824
        }
1825
1826
        $now = time();
1827
        if ($lp->hasCategory()) {
1828
            $category = $lp->getCategory();
1829
1830
            if (false === self::categoryIsVisibleForStudent(
1831
                    $category,
1832
                    api_get_user_entity($student_id),
1833
                    $course,
1834
                    $session
1835
                )) {
1836
                return false;
1837
            }
1838
1839
            $prerequisite = $lp->getPrerequisite();
1840
            $is_visible = true;
1841
1842
            $isBlocked = self::isBlockedByPrerequisite(
1843
                $student_id,
1844
                $prerequisite,
1845
                $course,
1846
                $sessionId
1847
            );
1848
1849
            if ($isBlocked) {
1850
                $is_visible = false;
1851
            }
1852
1853
            // Also check the time availability of the LP
1854
            if ($is_visible) {
1855
                // Adding visibility restrictions
1856
                if (null !== $lp->getPublicatedOn()) {
1857
                    if ($now < $lp->getPublicatedOn()->getTimestamp()) {
1858
                        $is_visible = false;
1859
                    }
1860
                }
1861
                // Blocking empty start times see BT#2800
1862
                global $_custom;
1863
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
1864
                    $_custom['lps_hidden_when_no_start_date']
1865
                ) {
1866
                    if (null !== $lp->getPublicatedOn()) {
1867
                        $is_visible = false;
1868
                    }
1869
                }
1870
1871
                if (null !== $lp->getExpiredOn()) {
1872
                    if ($now > $lp->getExpiredOn()->getTimestamp()) {
1873
                        $is_visible = false;
1874
                    }
1875
                }
1876
            }
1877
1878
            if ($is_visible) {
1879
                $subscriptionSettings = self::getSubscriptionSettings();
1880
1881
                // Check if the subscription users/group to a LP is ON
1882
                if (1 == $lp->getSubscribeUsers() &&
1883
                    true === $subscriptionSettings['allow_add_users_to_lp']
1884
                ) {
1885
                    // Try group
1886
                    $is_visible = false;
1887
                    // Checking only the user visibility
1888
                    // @todo fix visibility
1889
                    $userVisibility = 1;
1890
                    if (1 == $userVisibility) {
1891
                        $is_visible = true;
1892
                    } else {
1893
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
1894
                        if (!empty($userGroups)) {
1895
                            foreach ($userGroups as $groupInfo) {
1896
                                $groupId = $groupInfo['iid'];
1897
                                // @todo fix visibility.
1898
                                $userVisibility = 1;
1899
                                if (1 == $userVisibility) {
1900
                                    $is_visible = true;
1901
                                    break;
1902
                                }
1903
                            }
1904
                        }
1905
                    }
1906
                }
1907
            }
1908
1909
            return $is_visible;
1910
        }
1911
1912
        return true;
1913
    }
1914
1915
    /**
1916
     * @param int $lpId
1917
     * @param int $userId
1918
     * @param int $courseId
1919
     * @param int $sessionId
1920
     *
1921
     * @return int
1922
     */
1923
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
1924
    {
1925
        $lpId = (int) $lpId;
1926
        $userId = (int) $userId;
1927
        $courseId = (int) $courseId;
1928
        $sessionId = (int) $sessionId;
1929
1930
        $sessionCondition = api_get_session_condition($sessionId);
1931
        $table = Database::get_course_table(TABLE_LP_VIEW);
1932
        $sql = "SELECT progress FROM $table
1933
                WHERE
1934
                    c_id = $courseId AND
1935
                    lp_id = $lpId AND
1936
                    user_id = $userId $sessionCondition ";
1937
        $res = Database::query($sql);
1938
1939
        $progress = 0;
1940
        if (Database::num_rows($res) > 0) {
1941
            $row = Database::fetch_array($res);
1942
            $progress = (int) $row['progress'];
1943
        }
1944
1945
        return $progress;
1946
    }
1947
1948
    /**
1949
     * @param array $lpList
1950
     * @param int   $userId
1951
     * @param int   $courseId
1952
     * @param int   $sessionId
1953
     *
1954
     * @return array
1955
     */
1956
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
1957
    {
1958
        $lpList = array_map('intval', $lpList);
1959
        if (empty($lpList)) {
1960
            return [];
1961
        }
1962
1963
        $lpList = implode("','", $lpList);
1964
1965
        $userId = (int) $userId;
1966
        $courseId = (int) $courseId;
1967
        $sessionId = (int) $sessionId;
1968
1969
        $sessionCondition = api_get_session_condition($sessionId);
1970
        $table = Database::get_course_table(TABLE_LP_VIEW);
1971
        $sql = "SELECT lp_id, progress FROM $table
1972
                WHERE
1973
                    c_id = $courseId AND
1974
                    lp_id IN ('".$lpList."') AND
1975
                    user_id = $userId $sessionCondition ";
1976
        $res = Database::query($sql);
1977
1978
        if (Database::num_rows($res) > 0) {
1979
            $list = [];
1980
            while ($row = Database::fetch_array($res)) {
1981
                $list[$row['lp_id']] = $row['progress'];
1982
            }
1983
1984
            return $list;
1985
        }
1986
1987
        return [];
1988
    }
1989
1990
    /**
1991
     * Displays a progress bar
1992
     * completed so far.
1993
     *
1994
     * @param int    $percentage Progress value to display
1995
     * @param string $text_add   Text to display near the progress value
1996
     *
1997
     * @return string HTML string containing the progress bar
1998
     */
1999
    public static function get_progress_bar($percentage = -1, $text_add = '')
2000
    {
2001
        $text = $percentage.$text_add;
2002
2003
        return '<div class="progress">
2004
            <div id="progress_bar_value"
2005
                class="progress-bar progress-bar-success" role="progressbar"
2006
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2007
            '.$text.'
2008
            </div>
2009
        </div>';
2010
    }
2011
2012
    /**
2013
     * @param string $mode can be '%' or 'abs'
2014
     *                     otherwise this value will be used $this->progress_bar_mode
2015
     *
2016
     * @return string
2017
     */
2018
    public function getProgressBar($mode = null)
2019
    {
2020
        [$percentage, $text_add] = $this->get_progress_bar_text($mode);
2021
2022
        return self::get_progress_bar($percentage, $text_add);
2023
    }
2024
2025
    /**
2026
     * Gets the progress bar info to display inside the progress bar.
2027
     * Also used by scorm_api.php.
2028
     *
2029
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2030
     *                     we display a number of completed elements per total elements
2031
     * @param int    $add  Additional steps to fake as completed
2032
     *
2033
     * @return array Percentage or number and symbol (% or /xx)
2034
     */
2035
    public function get_progress_bar_text($mode = '', $add = 0)
2036
    {
2037
        if (empty($mode)) {
2038
            $mode = $this->progress_bar_mode;
2039
        }
2040
        $text = '';
2041
        $percentage = 0;
2042
        // If the option to use the score as progress is set for this learning
2043
        // path, then the rules are completely different: we assume only one
2044
        // item exists and the progress of the LP depends on the score
2045
        $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
2046
        if (true === $scoreAsProgressSetting) {
2047
            $scoreAsProgress = $this->getUseScoreAsProgress();
2048
            if ($scoreAsProgress) {
2049
                // Get single item's score
2050
                $itemId = $this->get_current_item_id();
2051
                $item = $this->getItem($itemId);
2052
                $score = $item->get_score();
2053
                $maxScore = $item->get_max();
2054
                if ($mode = '%') {
2055
                    if (!empty($maxScore)) {
2056
                        $percentage = ((float) $score / (float) $maxScore) * 100;
2057
                    }
2058
                    $percentage = number_format($percentage, 0);
2059
                    $text = '%';
2060
                } else {
2061
                    $percentage = $score;
2062
                    $text = '/'.$maxScore;
2063
                }
2064
2065
                return [$percentage, $text];
2066
            }
2067
        }
2068
        // otherwise just continue the normal processing of progress
2069
        $total_items = $this->getTotalItemsCountWithoutDirs();
2070
        $completeItems = $this->get_complete_items_count();
2071
        if (0 != $add) {
2072
            $completeItems += $add;
2073
        }
2074
        if ($completeItems > $total_items) {
2075
            $completeItems = $total_items;
2076
        }
2077
        if ('%' === $mode) {
2078
            if ($total_items > 0) {
2079
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2080
            }
2081
            $percentage = number_format($percentage, 0);
2082
            $text = '%';
2083
        } elseif ('abs' === $mode) {
2084
            $percentage = $completeItems;
2085
            $text = '/'.$total_items;
2086
        }
2087
2088
        return [
2089
            $percentage,
2090
            $text,
2091
        ];
2092
    }
2093
2094
    /**
2095
     * Gets the progress bar mode.
2096
     *
2097
     * @return string The progress bar mode attribute
2098
     */
2099
    public function get_progress_bar_mode()
2100
    {
2101
        if (!empty($this->progress_bar_mode)) {
2102
            return $this->progress_bar_mode;
2103
        }
2104
2105
        return '%';
2106
    }
2107
2108
    /**
2109
     * Gets the learnpath theme (remote or local).
2110
     *
2111
     * @return string Learnpath theme
2112
     */
2113
    public function get_theme()
2114
    {
2115
        if (!empty($this->theme)) {
2116
            return $this->theme;
2117
        }
2118
2119
        return '';
2120
    }
2121
2122
    /**
2123
     * Gets the learnpath session id.
2124
     *
2125
     * @return int
2126
     */
2127
    public function get_lp_session_id()
2128
    {
2129
        if (!empty($this->lp_session_id)) {
2130
            return (int) $this->lp_session_id;
2131
        }
2132
2133
        return 0;
2134
    }
2135
2136
    /**
2137
     * Generate a new prerequisites string for a given item. If this item was a sco and
2138
     * its prerequisites were strings (instead of IDs), then transform those strings into
2139
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2140
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2141
     * same rule as the scormExport() method.
2142
     *
2143
     * @param int $item_id Item ID
2144
     *
2145
     * @return string Prerequisites string ready for the export as SCORM
2146
     */
2147
    public function get_scorm_prereq_string($item_id)
2148
    {
2149
        if ($this->debug > 0) {
2150
            error_log('In learnpath::get_scorm_prereq_string()');
2151
        }
2152
        if (!is_object($this->items[$item_id])) {
2153
            return false;
2154
        }
2155
        /** @var learnpathItem $oItem */
2156
        $oItem = $this->items[$item_id];
2157
        $prereq = $oItem->get_prereq_string();
2158
2159
        if (empty($prereq)) {
2160
            return '';
2161
        }
2162
        if (preg_match('/^\d+$/', $prereq) &&
2163
            isset($this->items[$prereq]) &&
2164
            is_object($this->items[$prereq])
2165
        ) {
2166
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2167
            // then simply return it (with the ITEM_ prefix).
2168
            //return 'ITEM_' . $prereq;
2169
            return $this->items[$prereq]->ref;
2170
        } else {
2171
            if (isset($this->refs_list[$prereq])) {
2172
                // It's a simple string item from which the ID can be found in the refs list,
2173
                // so we can transform it directly to an ID for export.
2174
                return $this->items[$this->refs_list[$prereq]]->ref;
2175
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2176
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2177
            } else {
2178
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2179
                // and replace them, one by one, by the internal IDs (chamilo db)
2180
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2181
                // by a space as well.
2182
                $find = [
2183
                    '&',
2184
                    '|',
2185
                    '~',
2186
                    '=',
2187
                    '<>',
2188
                    '{',
2189
                    '}',
2190
                    '*',
2191
                    '(',
2192
                    ')',
2193
                ];
2194
                $replace = [
2195
                    ' ',
2196
                    ' ',
2197
                    ' ',
2198
                    ' ',
2199
                    ' ',
2200
                    ' ',
2201
                    ' ',
2202
                    ' ',
2203
                    ' ',
2204
                    ' ',
2205
                ];
2206
                $prereq_mod = str_replace($find, $replace, $prereq);
2207
                $ids = explode(' ', $prereq_mod);
2208
                foreach ($ids as $id) {
2209
                    $id = trim($id);
2210
                    if (isset($this->refs_list[$id])) {
2211
                        $prereq = preg_replace(
2212
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2213
                            'ITEM_'.$this->refs_list[$id],
2214
                            $prereq
2215
                        );
2216
                    }
2217
                }
2218
2219
                return $prereq;
2220
            }
2221
        }
2222
    }
2223
2224
    /**
2225
     * Returns the XML DOM document's node.
2226
     *
2227
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2228
     * @param string   $id       The identifier to look for
2229
     *
2230
     * @return mixed The reference to the element found with that identifier. False if not found
2231
     */
2232
    public function get_scorm_xml_node(&$children, $id)
2233
    {
2234
        for ($i = 0; $i < $children->length; $i++) {
2235
            $item_temp = $children->item($i);
2236
            if ('item' === $item_temp->nodeName) {
2237
                if ($item_temp->getAttribute('identifier') == $id) {
2238
                    return $item_temp;
2239
                }
2240
            }
2241
            $subchildren = $item_temp->childNodes;
2242
            if ($subchildren && $subchildren->length > 0) {
2243
                $val = $this->get_scorm_xml_node($subchildren, $id);
2244
                if (is_object($val)) {
2245
                    return $val;
2246
                }
2247
            }
2248
        }
2249
2250
        return false;
2251
    }
2252
2253
    /**
2254
     * Gets the status list for all LP's items.
2255
     *
2256
     * @return array Array of [index] => [item ID => current status]
2257
     */
2258
    public function get_items_status_list()
2259
    {
2260
        $list = [];
2261
        foreach ($this->ordered_items as $item_id) {
2262
            $list[] = [
2263
                $item_id => $this->items[$item_id]->get_status(),
2264
            ];
2265
        }
2266
2267
        return $list;
2268
    }
2269
2270
    /**
2271
     * Return the number of interactions for the given learnpath Item View ID.
2272
     * This method can be used as static.
2273
     *
2274
     * @param int $lp_iv_id  Item View ID
2275
     * @param int $course_id course id
2276
     *
2277
     * @return int
2278
     */
2279
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2280
    {
2281
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2282
        $lp_iv_id = (int) $lp_iv_id;
2283
        $course_id = (int) $course_id;
2284
2285
        $sql = "SELECT count(*) FROM $table
2286
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2287
        $res = Database::query($sql);
2288
        $num = 0;
2289
        if (Database::num_rows($res)) {
2290
            $row = Database::fetch_array($res);
2291
            $num = $row[0];
2292
        }
2293
2294
        return $num;
2295
    }
2296
2297
    /**
2298
     * Return the interactions as an array for the given lp_iv_id.
2299
     * This method can be used as static.
2300
     *
2301
     * @param int $lp_iv_id Learnpath Item View ID
2302
     *
2303
     * @return array
2304
     *
2305
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2306
     */
2307
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2308
    {
2309
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2310
        $list = [];
2311
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2312
        $lp_iv_id = (int) $lp_iv_id;
2313
2314
        if (empty($lp_iv_id) || empty($course_id)) {
2315
            return [];
2316
        }
2317
2318
        $sql = "SELECT * FROM $table
2319
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2320
                ORDER BY order_id ASC";
2321
        $res = Database::query($sql);
2322
        $num = Database::num_rows($res);
2323
        if ($num > 0) {
2324
            $list[] = [
2325
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2326
                'id' => api_htmlentities(get_lang('Interaction ID'), ENT_QUOTES),
2327
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2328
                'time' => api_htmlentities(get_lang('Time (finished at...)'), ENT_QUOTES),
2329
                'correct_responses' => api_htmlentities(get_lang('Correct answers'), ENT_QUOTES),
2330
                'student_response' => api_htmlentities(get_lang('Learner answers'), ENT_QUOTES),
2331
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2332
                'latency' => api_htmlentities(get_lang('Time spent'), ENT_QUOTES),
2333
                'student_response_formatted' => '',
2334
            ];
2335
            while ($row = Database::fetch_array($res)) {
2336
                $studentResponseFormatted = urldecode($row['student_response']);
2337
                $content_student_response = explode('__|', $studentResponseFormatted);
2338
                if (count($content_student_response) > 0) {
2339
                    if (count($content_student_response) >= 3) {
2340
                        // Pop the element off the end of array.
2341
                        array_pop($content_student_response);
2342
                    }
2343
                    $studentResponseFormatted = implode(',', $content_student_response);
2344
                }
2345
2346
                $list[] = [
2347
                    'order_id' => $row['order_id'] + 1,
2348
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2349
                    'type' => $row['interaction_type'],
2350
                    'time' => $row['completion_time'],
2351
                    'correct_responses' => '', // Hide correct responses from students.
2352
                    'student_response' => $row['student_response'],
2353
                    'result' => $row['result'],
2354
                    'latency' => $row['latency'],
2355
                    'student_response_formatted' => $studentResponseFormatted,
2356
                ];
2357
            }
2358
        }
2359
2360
        return $list;
2361
    }
2362
2363
    /**
2364
     * Return the number of objectives for the given learnpath Item View ID.
2365
     * This method can be used as static.
2366
     *
2367
     * @param int $lp_iv_id  Item View ID
2368
     * @param int $course_id Course ID
2369
     *
2370
     * @return int Number of objectives
2371
     */
2372
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2373
    {
2374
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2375
        $course_id = (int) $course_id;
2376
        $lp_iv_id = (int) $lp_iv_id;
2377
        $sql = "SELECT count(*) FROM $table
2378
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2379
        //@todo seems that this always returns 0
2380
        $res = Database::query($sql);
2381
        $num = 0;
2382
        if (Database::num_rows($res)) {
2383
            $row = Database::fetch_array($res);
2384
            $num = $row[0];
2385
        }
2386
2387
        return $num;
2388
    }
2389
2390
    /**
2391
     * Return the objectives as an array for the given lp_iv_id.
2392
     * This method can be used as static.
2393
     *
2394
     * @param int $lpItemViewId Learnpath Item View ID
2395
     * @param int $course_id
2396
     *
2397
     * @return array
2398
     *
2399
     * @todo    Translate labels
2400
     */
2401
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
2402
    {
2403
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2404
        $lpItemViewId = (int) $lpItemViewId;
2405
2406
        if (empty($course_id) || empty($lpItemViewId)) {
2407
            return [];
2408
        }
2409
2410
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2411
        $sql = "SELECT * FROM $table
2412
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
2413
                ORDER BY order_id ASC";
2414
        $res = Database::query($sql);
2415
        $num = Database::num_rows($res);
2416
        $list = [];
2417
        if ($num > 0) {
2418
            $list[] = [
2419
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2420
                'objective_id' => api_htmlentities(get_lang('Objective ID'), ENT_QUOTES),
2421
                'score_raw' => api_htmlentities(get_lang('Objective raw score'), ENT_QUOTES),
2422
                'score_max' => api_htmlentities(get_lang('Objective max score'), ENT_QUOTES),
2423
                'score_min' => api_htmlentities(get_lang('Objective min score'), ENT_QUOTES),
2424
                'status' => api_htmlentities(get_lang('Objective status'), ENT_QUOTES),
2425
            ];
2426
            while ($row = Database::fetch_array($res)) {
2427
                $list[] = [
2428
                    'order_id' => $row['order_id'] + 1,
2429
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
2430
                    'score_raw' => $row['score_raw'],
2431
                    'score_max' => $row['score_max'],
2432
                    'score_min' => $row['score_min'],
2433
                    'status' => $row['status'],
2434
                ];
2435
            }
2436
        }
2437
2438
        return $list;
2439
    }
2440
2441
    /**
2442
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
2443
     * used by get_html_toc() to be ready to display.
2444
     *
2445
     * @return array TOC as a table with 4 elements per row: title, link, status and level
2446
     */
2447
    public function get_toc()
2448
    {
2449
        $toc = [];
2450
        foreach ($this->ordered_items as $item_id) {
2451
            // TODO: Change this link generation and use new function instead.
2452
            $toc[] = [
2453
                'id' => $item_id,
2454
                'title' => $this->items[$item_id]->get_title(),
2455
                'status' => $this->items[$item_id]->get_status(),
2456
                'status_class' => self::getStatusCSSClassName($this->items[$item_id]->get_status()),
2457
                'level' => $this->items[$item_id]->get_level(),
2458
                'type' => $this->items[$item_id]->get_type(),
2459
                'description' => $this->items[$item_id]->get_description(),
2460
                'path' => $this->items[$item_id]->get_path(),
2461
                'parent' => $this->items[$item_id]->get_parent(),
2462
            ];
2463
        }
2464
2465
        return $toc;
2466
    }
2467
2468
    /**
2469
     * Returns the CSS class name associated with a given item status.
2470
     *
2471
     * @param $status string an item status
2472
     *
2473
     * @return string CSS class name
2474
     */
2475
    public static function getStatusCSSClassName($status)
2476
    {
2477
        if (array_key_exists($status, self::STATUS_CSS_CLASS_NAME)) {
2478
            return self::STATUS_CSS_CLASS_NAME[$status];
2479
        }
2480
2481
        return '';
2482
    }
2483
2484
    /**
2485
     * Generate and return the table of contents for this learnpath. The JS
2486
     * table returned is used inside of scorm_api.php.
2487
     *
2488
     * @param string $varname
2489
     *
2490
     * @return string A JS array variable construction
2491
     */
2492
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
2493
    {
2494
        $toc = $varname.' = new Array();';
2495
        foreach ($this->ordered_items as $item_id) {
2496
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
2497
        }
2498
2499
        return $toc;
2500
    }
2501
2502
    /**
2503
     * Gets the learning path type.
2504
     *
2505
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
2506
     *
2507
     * @return mixed Type ID or name, depending on the parameter
2508
     */
2509
    public function get_type($get_name = false)
2510
    {
2511
        $res = false;
2512
        if (!empty($this->type) && (!$get_name)) {
2513
            $res = $this->type;
2514
        }
2515
2516
        return $res;
2517
    }
2518
2519
    /**
2520
     * Gets the learning path type as static method.
2521
     *
2522
     * @param int $lp_id
2523
     *
2524
     * @return mixed Type ID or name, depending on the parameter
2525
     */
2526
    public static function get_type_static($lp_id = 0)
2527
    {
2528
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
2529
        $lp_id = (int) $lp_id;
2530
        $sql = "SELECT lp_type FROM $tbl_lp
2531
                WHERE iid = $lp_id";
2532
        $res = Database::query($sql);
2533
        if (false === $res) {
2534
            return null;
2535
        }
2536
        if (Database::num_rows($res) <= 0) {
2537
            return null;
2538
        }
2539
        $row = Database::fetch_array($res);
2540
2541
        return $row['lp_type'];
2542
    }
2543
2544
    /**
2545
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
2546
     * This method can be used as abstract and is recursive.
2547
     *
2548
     * @param CLp $lp
2549
     * @param int $parent    Parent ID of the items to look for
2550
     *
2551
     * @return array Ordered list of item IDs (empty array on error)
2552
     */
2553
    public static function get_flat_ordered_items_list(CLp $lp, $parent = 0)
2554
    {
2555
        $parent = (int) $parent;
2556
        $lpItemRepo = Container::getLpItemRepository();
2557
        if (empty($parent)) {
2558
            $rootItem = $lpItemRepo->getRootItem($lp->getIid());
2559
            if (null !== $rootItem) {
2560
                $parent = $rootItem->getIid();
2561
            }
2562
        }
2563
2564
        if (empty($parent)) {
2565
            return [];
2566
        }
2567
2568
        $criteria = new Criteria();
2569
        $criteria
2570
            ->where($criteria->expr()->neq('path', 'root'))
2571
            ->orderBy(
2572
                [
2573
                    'displayOrder' => Criteria::ASC,
2574
                ]
2575
            );
2576
        $items = $lp->getItems()->matching($criteria);
2577
        $items = $items->filter(
2578
            function (CLpItem $element) use ($parent) {
2579
                if ('root' === $element->getPath()) {
2580
                    return false;
2581
                }
2582
2583
                if (null !== $element->getParent()) {
2584
                    return $element->getParent()->getIid() === $parent;
2585
                }
2586
                return false;
2587
2588
            }
2589
        );
2590
2591
        /*
2592
         $lp = (int) $lp;
2593
        $parent = (int) $parent;
2594
2595
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2596
        $sql = "SELECT iid FROM $tbl_lp_item
2597
                WHERE lp_id = $lp AND parent_item_id = $parent
2598
                ORDER BY display_order";
2599
2600
        $res = Database::query($sql);
2601
        while ($row = Database::fetch_array($res)) {
2602
            $sublist = self::get_flat_ordered_items_list(
2603
                $lp,
2604
                $row['iid'],
2605
                $course_id
2606
            );
2607
            $list[] = $row['iid'];
2608
            foreach ($sublist as $item) {
2609
                $list[] = $item;
2610
            }
2611
        }
2612
        */
2613
2614
        $list = [];
2615
        foreach ($items as $item) {
2616
            $itemId = $item->getIid();
2617
            $sublist = self::get_flat_ordered_items_list($lp, $itemId);
2618
            $list[] = $itemId;
2619
            foreach ($sublist as $subItem) {
2620
                $list[] = $subItem;
2621
            }
2622
        }
2623
2624
        return $list;
2625
    }
2626
2627
    public static function getChapterTypes(): array
2628
    {
2629
        return [
2630
            'dir',
2631
        ];
2632
    }
2633
2634
    /**
2635
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
2636
     *
2637
     * @param array $toc_list
2638
     *
2639
     * @return array HTML TOC ready to display
2640
     */
2641
    public function getListArrayToc()
2642
    {
2643
        $lpItemRepo = Container::getLpItemRepository();
2644
        $itemRoot = $lpItemRepo->getRootItem($this->get_id());
2645
        $options = [
2646
            'decorate' => false,
2647
        ];
2648
2649
        return $lpItemRepo->childrenHierarchy($itemRoot, false, $options);
2650
    }
2651
2652
    /**
2653
     * Returns an HTML-formatted string ready to display with teacher buttons
2654
     * in LP view menu.
2655
     *
2656
     * @return string HTML TOC ready to display
2657
     */
2658
    public function get_teacher_toc_buttons()
2659
    {
2660
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
2661
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
2662
        $html = '';
2663
        if ($isAllow && false == $hideIcons) {
2664
            if ($this->get_lp_session_id() == api_get_session_id()) {
2665
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
2666
                $html .= '<div class="flex flex-row justify-center">';
2667
                $html .= "<a
2668
                    class='btn btn-sm btn-default'
2669
                    href='lp_controller.php?".api_get_cidreq()."&action=build&lp_id=".$this->lp_id."&isStudentView=false'
2670
                    target='_parent'>".
2671
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
2672
                $html .= "<a
2673
                    class='btn btn-sm btn-default'
2674
                    href='lp_controller.php?".api_get_cidreq()."&action=add_item&type=step&lp_id=".$this->lp_id."&isStudentView=false'
2675
                    target='_parent'>".
2676
                    Display::returnFontAwesomeIcon('pencil-alt').get_lang('Edit')."</a>";
2677
                $html .= '<a
2678
                    class="btn btn-sm btn-default"
2679
                    href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
2680
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
2681
                $html .= '</div>';
2682
                $html .= '</div>';
2683
            }
2684
        }
2685
2686
        return $html;
2687
    }
2688
2689
    /**
2690
     * Gets the learnpath name/title.
2691
     *
2692
     * @return string Learnpath name/title
2693
     */
2694
    public function get_name()
2695
    {
2696
        if (!empty($this->name)) {
2697
            return $this->name;
2698
        }
2699
2700
        return 'N/A';
2701
    }
2702
2703
    /**
2704
     * @return string
2705
     */
2706
    public function getNameNoTags()
2707
    {
2708
        return strip_tags($this->get_name());
2709
    }
2710
2711
    /**
2712
     * Gets a link to the resource from the present location, depending on item ID.
2713
     *
2714
     * @param string $type         Type of link expected
2715
     * @param int    $item_id      Learnpath item ID
2716
     * @param bool   $provided_toc
2717
     *
2718
     * @return string $provided_toc Link to the lp_item resource
2719
     */
2720
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
2721
    {
2722
        $course_id = $this->get_course_int_id();
2723
        $item_id = (int) $item_id;
2724
2725
        if (empty($item_id)) {
2726
            $item_id = $this->get_current_item_id();
2727
2728
            if (empty($item_id)) {
2729
                //still empty, this means there was no item_id given and we are not in an object context or
2730
                //the object property is empty, return empty link
2731
                $this->first();
2732
2733
                return '';
2734
            }
2735
        }
2736
2737
        $file = '';
2738
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
2739
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
2740
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2741
2742
        $sql = "SELECT
2743
                    l.lp_type as ltype,
2744
                    l.path as lpath,
2745
                    li.item_type as litype,
2746
                    li.path as lipath,
2747
                    li.parameters as liparams
2748
        		FROM $lp_table l
2749
                INNER JOIN $lp_item_table li
2750
                ON (li.lp_id = l.iid)
2751
        		WHERE
2752
        		    li.iid = $item_id
2753
        		";
2754
        $res = Database::query($sql);
2755
        if (Database::num_rows($res) > 0) {
2756
            $row = Database::fetch_array($res);
2757
            $lp_type = $row['ltype'];
2758
            $lp_path = $row['lpath'];
2759
            $lp_item_type = $row['litype'];
2760
            $lp_item_path = $row['lipath'];
2761
            $lp_item_params = $row['liparams'];
2762
            if (empty($lp_item_params) && false !== strpos($lp_item_path, '?')) {
2763
                [$lp_item_path, $lp_item_params] = explode('?', $lp_item_path);
2764
            }
2765
            //$sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
2766
            if ('http' === $type) {
2767
                //web path
2768
                //$course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
2769
            } else {
2770
                //$course_path = $sys_course_path; //system path
2771
            }
2772
2773
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
2774
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
2775
            if (in_array(
2776
                $lp_item_type,
2777
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
2778
            )
2779
            ) {
2780
                $lp_type = CLp::LP_TYPE;
2781
            }
2782
2783
            // Now go through the specific cases to get the end of the path
2784
            // @todo Use constants instead of int values.
2785
            switch ($lp_type) {
2786
                case CLp::LP_TYPE:
2787
                    $file = self::rl_get_resource_link_for_learnpath(
2788
                        $course_id,
2789
                        $this->get_id(),
2790
                        $item_id,
2791
                        $this->get_view_id()
2792
                    );
2793
                    switch ($lp_item_type) {
2794
                        case 'document':
2795
                            // Shows a button to download the file instead of just downloading the file directly.
2796
                            $documentPathInfo = pathinfo($file);
2797
                            if (isset($documentPathInfo['extension'])) {
2798
                                $parsed = parse_url($documentPathInfo['extension']);
2799
                                if (isset($parsed['path'])) {
2800
                                    $extension = $parsed['path'];
2801
                                    $extensionsToDownload = [
2802
                                        'zip',
2803
                                        'ppt',
2804
                                        'pptx',
2805
                                        'ods',
2806
                                        'xlsx',
2807
                                        'xls',
2808
                                        'csv',
2809
                                        'doc',
2810
                                        'docx',
2811
                                        'dot',
2812
                                    ];
2813
2814
                                    if (in_array($extension, $extensionsToDownload)) {
2815
                                        $file = api_get_path(WEB_CODE_PATH).
2816
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
2817
                                    }
2818
                                }
2819
                            }
2820
                            break;
2821
                        case 'dir':
2822
                            $file = 'lp_content.php?type=dir';
2823
                            break;
2824
                        case 'link':
2825
                            if (Link::is_youtube_link($file)) {
2826
                                $src = Link::get_youtube_video_id($file);
2827
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
2828
                            } elseif (Link::isVimeoLink($file)) {
2829
                                $src = Link::getVimeoLinkId($file);
2830
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
2831
                            } else {
2832
                                // If the current site is HTTPS and the link is
2833
                                // HTTP, browsers will refuse opening the link
2834
                                $urlId = api_get_current_access_url_id();
2835
                                $url = api_get_access_url($urlId, false);
2836
                                $protocol = substr($url['url'], 0, 5);
2837
                                if ('https' === $protocol) {
2838
                                    $linkProtocol = substr($file, 0, 5);
2839
                                    if ('http:' === $linkProtocol) {
2840
                                        //this is the special intervention case
2841
                                        $file = api_get_path(WEB_CODE_PATH).
2842
                                            'lp/embed.php?type=nonhttps&source='.urlencode($file);
2843
                                    }
2844
                                }
2845
                            }
2846
                            break;
2847
                        case 'quiz':
2848
                            // Check how much attempts of a exercise exits in lp
2849
                            $lp_item_id = $this->get_current_item_id();
2850
                            $lp_view_id = $this->get_view_id();
2851
2852
                            $prevent_reinit = null;
2853
                            if (isset($this->items[$this->current])) {
2854
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
2855
                            }
2856
2857
                            if (empty($provided_toc)) {
2858
                                $list = $this->get_toc();
2859
                            } else {
2860
                                $list = $provided_toc;
2861
                            }
2862
2863
                            $type_quiz = false;
2864
                            foreach ($list as $toc) {
2865
                                if ($toc['id'] == $lp_item_id && 'quiz' === $toc['type']) {
2866
                                    $type_quiz = true;
2867
                                }
2868
                            }
2869
2870
                            if ($type_quiz) {
2871
                                $lp_item_id = (int) $lp_item_id;
2872
                                $lp_view_id = (int) $lp_view_id;
2873
                                $sql = "SELECT count(*) FROM $lp_item_view_table
2874
                                        WHERE
2875
                                            lp_item_id='".$lp_item_id."' AND
2876
                                            lp_view_id ='".$lp_view_id."' AND
2877
                                            status='completed'";
2878
                                $result = Database::query($sql);
2879
                                $row_count = Database:: fetch_row($result);
2880
                                $count_item_view = (int) $row_count[0];
2881
                                $not_multiple_attempt = 0;
2882
                                if (1 === $prevent_reinit && $count_item_view > 0) {
2883
                                    $not_multiple_attempt = 1;
2884
                                }
2885
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
2886
                            }
2887
                            break;
2888
                    }
2889
2890
                    $tmp_array = explode('/', $file);
2891
                    $document_name = $tmp_array[count($tmp_array) - 1];
2892
                    if (strpos($document_name, '_DELETED_')) {
2893
                        $file = 'blank.php?error=document_deleted';
2894
                    }
2895
                    break;
2896
                case CLp::SCORM_TYPE:
2897
                    if ('dir' !== $lp_item_type) {
2898
                        // Quite complex here:
2899
                        // We want to make sure 'http://' (and similar) links can
2900
                        // be loaded as is (withouth the Chamilo path in front) but
2901
                        // some contents use this form: resource.htm?resource=http://blablabla
2902
                        // which means we have to find a protocol at the path's start, otherwise
2903
                        // it should not be considered as an external URL.
2904
                        // if ($this->prerequisites_match($item_id)) {
2905
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
2906
                            if ($this->debug > 2) {
2907
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
2908
                            }
2909
                            // Distant url, return as is.
2910
                            $file = $lp_item_path;
2911
                        } else {
2912
                            if ($this->debug > 2) {
2913
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path);
2914
                            }
2915
                            // Prevent getting untranslatable urls.
2916
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
2917
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
2918
2919
                            /*$asset = $this->getEntity()->getAsset();
2920
                            $folder = Container::getAssetRepository()->getFolder($asset);
2921
                            $hasFile = Container::getAssetRepository()->getFileSystem()->has($folder.$lp_item_path);
2922
                            $file = null;
2923
                            if ($hasFile) {
2924
                                $file = Container::getAssetRepository()->getAssetUrl($asset).'/'.$lp_item_path;
2925
                            }*/
2926
                            $file = $this->scormUrl.$lp_item_path;
2927
2928
                            // Prepare the path.
2929
                            /*$file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
2930
                            // TODO: Fix this for urls with protocol header.
2931
                            $file = str_replace('//', '/', $file);
2932
                            $file = str_replace(':/', '://', $file);
2933
                            if ('/' === substr($lp_path, -1)) {
2934
                                $lp_path = substr($lp_path, 0, -1);
2935
                            }*/
2936
                            /*if (!$hasFile) {
2937
                                // if file not found.
2938
                                $decoded = html_entity_decode($lp_item_path);
2939
                                [$decoded] = explode('?', $decoded);
2940
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
2941
                                    $file = self::rl_get_resource_link_for_learnpath(
2942
                                        $course_id,
2943
                                        $this->get_id(),
2944
                                        $item_id,
2945
                                        $this->get_view_id()
2946
                                    );
2947
                                    if (empty($file)) {
2948
                                        $file = 'blank.php?error=document_not_found';
2949
                                    } else {
2950
                                        $tmp_array = explode('/', $file);
2951
                                        $document_name = $tmp_array[count($tmp_array) - 1];
2952
                                        if (strpos($document_name, '_DELETED_')) {
2953
                                            $file = 'blank.php?error=document_deleted';
2954
                                        } else {
2955
                                            $file = 'blank.php?error=document_not_found';
2956
                                        }
2957
                                    }
2958
                                } else {
2959
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
2960
                                }
2961
                            }*/
2962
                        }
2963
2964
                        // We want to use parameters if they were defined in the imsmanifest
2965
                        if (false === strpos($file, 'blank.php')) {
2966
                            $lp_item_params = ltrim($lp_item_params, '?');
2967
                            $file .= (false === strstr($file, '?') ? '?' : '').$lp_item_params;
2968
                        }
2969
                    } else {
2970
                        $file = 'lp_content.php?type=dir';
2971
                    }
2972
                    break;
2973
                case CLp::AICC_TYPE:
2974
                    // Formatting AICC HACP append URL.
2975
                    $aicc_append = '?aicc_sid='.
2976
                        urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
2977
                    if (!empty($lp_item_params)) {
2978
                        $aicc_append .= $lp_item_params.'&';
2979
                    }
2980
                    if ('dir' !== $lp_item_type) {
2981
                        // Quite complex here:
2982
                        // We want to make sure 'http://' (and similar) links can
2983
                        // be loaded as is (withouth the Chamilo path in front) but
2984
                        // some contents use this form: resource.htm?resource=http://blablabla
2985
                        // which means we have to find a protocol at the path's start, otherwise
2986
                        // it should not be considered as an external URL.
2987
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
2988
                            if ($this->debug > 2) {
2989
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
2990
                            }
2991
                            // Distant url, return as is.
2992
                            $file = $lp_item_path;
2993
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
2994
                            /*
2995
                            if (stristr($file,'<servername>') !== false) {
2996
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
2997
                            }
2998
                            */
2999
                            if (false !== stripos($file, '<servername>')) {
3000
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3001
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3002
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3003
                            }
3004
3005
                            $file .= $aicc_append;
3006
                        } else {
3007
                            if ($this->debug > 2) {
3008
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3009
                            }
3010
                            // Prevent getting untranslatable urls.
3011
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3012
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3013
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3014
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3015
                            // TODO: Fix this for urls with protocol header.
3016
                            $file = str_replace('//', '/', $file);
3017
                            $file = str_replace(':/', '://', $file);
3018
                            $file .= $aicc_append;
3019
                        }
3020
                    } else {
3021
                        $file = 'lp_content.php?type=dir';
3022
                    }
3023
                    break;
3024
                case 4:
3025
                default:
3026
                    break;
3027
            }
3028
            // Replace &amp; by & because &amp; will break URL with params
3029
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3030
        }
3031
        if ($this->debug > 2) {
3032
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3033
        }
3034
3035
        return $file;
3036
    }
3037
3038
    /**
3039
     * Gets the latest usable view or generate a new one.
3040
     *
3041
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3042
     * @param int $userId      The user ID, as $this->get_user_id() is not always available
3043
     *
3044
     * @return int DB lp_view id
3045
     */
3046
    public function get_view($attempt_num = 0, $userId = null)
3047
    {
3048
        $search = '';
3049
        $attempt_num = (int) $attempt_num;
3050
        // Use $attempt_num to enable multi-views management (disabled so far).
3051
        if (!empty($attempt_num)) {
3052
            $search = 'AND view_count = '.$attempt_num;
3053
        }
3054
3055
        $course_id = api_get_course_int_id();
3056
        $sessionId = api_get_session_id();
3057
3058
        // Check user ID.
3059
        if (empty($userId)) {
3060
            if (empty($this->get_user_id())) {
3061
                $this->error = 'User ID is empty in learnpath::get_view()';
3062
3063
                return null;
3064
            } else {
3065
                $userId = $this->get_user_id();
3066
            }
3067
        }
3068
        $sessionCondition = api_get_session_condition($sessionId);
3069
3070
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3071
        $table = Database::get_course_table(TABLE_LP_VIEW);
3072
        $sql = "SELECT iid FROM $table
3073
        		WHERE
3074
        		    c_id = $course_id AND
3075
        		    lp_id = ".$this->get_id()." AND
3076
        		    user_id = ".$userId."
3077
        		    $sessionCondition
3078
        		    $search
3079
                ORDER BY view_count DESC";
3080
        $res = Database::query($sql);
3081
        if (Database::num_rows($res) > 0) {
3082
            $row = Database::fetch_array($res);
3083
            $this->lp_view_id = $row['iid'];
3084
        } elseif (!api_is_invitee()) {
3085
            $params = [
3086
                'c_id' => $course_id,
3087
                'lp_id' => $this->get_id(),
3088
                'user_id' => $this->get_user_id(),
3089
                'view_count' => 1,
3090
                'last_item' => 0,
3091
            ];
3092
            if (!empty($sessionId)) {
3093
                $params['session_id']  = $sessionId;
3094
            }
3095
            $this->lp_view_id = Database::insert($table, $params);
3096
        }
3097
3098
        return $this->lp_view_id;
3099
    }
3100
3101
    /**
3102
     * Gets the current view id.
3103
     *
3104
     * @return int View ID (from lp_view)
3105
     */
3106
    public function get_view_id()
3107
    {
3108
        if (!empty($this->lp_view_id)) {
3109
            return (int) $this->lp_view_id;
3110
        }
3111
3112
        return 0;
3113
    }
3114
3115
    /**
3116
     * Gets the update queue.
3117
     *
3118
     * @return array Array containing IDs of items to be updated by JavaScript
3119
     */
3120
    public function get_update_queue()
3121
    {
3122
        return $this->update_queue;
3123
    }
3124
3125
    /**
3126
     * Gets the user ID.
3127
     *
3128
     * @return int User ID
3129
     */
3130
    public function get_user_id()
3131
    {
3132
        if (!empty($this->user_id)) {
3133
            return (int) $this->user_id;
3134
        }
3135
3136
        return false;
3137
    }
3138
3139
    /**
3140
     * Checks if any of the items has an audio element attached.
3141
     *
3142
     * @return bool True or false
3143
     */
3144
    public function has_audio()
3145
    {
3146
        $has = false;
3147
        foreach ($this->items as $i => $item) {
3148
            if (!empty($this->items[$i]->audio)) {
3149
                $has = true;
3150
                break;
3151
            }
3152
        }
3153
3154
        return $has;
3155
    }
3156
3157
    /**
3158
     * Updates learnpath attributes to point to the next element
3159
     * The last part is similar to set_current_item but processing the other way around.
3160
     */
3161
    public function next()
3162
    {
3163
        if ($this->debug > 0) {
3164
            error_log('In learnpath::next()', 0);
3165
        }
3166
        $this->last = $this->get_current_item_id();
3167
        $this->items[$this->last]->save(
3168
            false,
3169
            $this->prerequisites_match($this->last)
3170
        );
3171
        $this->autocomplete_parents($this->last);
3172
        $new_index = $this->get_next_index();
3173
        if ($this->debug > 2) {
3174
            error_log('New index: '.$new_index, 0);
3175
        }
3176
        $this->index = $new_index;
3177
        if ($this->debug > 2) {
3178
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
3179
        }
3180
        $this->current = $this->ordered_items[$new_index];
3181
        if ($this->debug > 2) {
3182
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
3183
        }
3184
    }
3185
3186
    /**
3187
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
3188
     * class, this might be redefined to allow several behaviours depending on the document type.
3189
     *
3190
     * @param int $id Resource ID
3191
     */
3192
    public function open($id)
3193
    {
3194
        // TODO:
3195
        // set the current resource attribute to this resource
3196
        // switch on element type (redefine in child class?)
3197
        // set status for this item to "opened"
3198
        // start timer
3199
        // initialise score
3200
        $this->index = 0; //or = the last item seen (see $this->last)
3201
    }
3202
3203
    /**
3204
     * Check that all prerequisites are fulfilled. Returns true and an
3205
     * empty string on success, returns false
3206
     * and the prerequisite string on error.
3207
     * This function is based on the rules for aicc_script language as
3208
     * described in the SCORM 1.2 CAM documentation page 108.
3209
     *
3210
     * @param int $itemId Optional item ID. If none given, uses the current open item.
3211
     *
3212
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
3213
     *              string otherwise
3214
     */
3215
    public function prerequisites_match($itemId = null)
3216
    {
3217
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
3218
        if ($allow) {
3219
            if (api_is_allowed_to_edit() ||
3220
                api_is_platform_admin(true) ||
3221
                api_is_drh() ||
3222
                api_is_coach(api_get_session_id(), api_get_course_int_id())
3223
            ) {
3224
                return true;
3225
            }
3226
        }
3227
3228
        $debug = $this->debug;
3229
        if ($debug > 0) {
3230
            error_log('In learnpath::prerequisites_match()');
3231
        }
3232
3233
        if (empty($itemId)) {
3234
            $itemId = $this->current;
3235
        }
3236
3237
        $currentItem = $this->getItem($itemId);
3238
3239
        if ($currentItem) {
3240
            if (2 == $this->type) {
3241
                // Getting prereq from scorm
3242
                $prereq_string = $this->get_scorm_prereq_string($itemId);
3243
            } else {
3244
                $prereq_string = $currentItem->get_prereq_string();
3245
            }
3246
3247
            if (empty($prereq_string)) {
3248
                if ($debug > 0) {
3249
                    error_log('Found prereq_string is empty return true');
3250
                }
3251
3252
                return true;
3253
            }
3254
3255
            // Clean spaces.
3256
            $prereq_string = str_replace(' ', '', $prereq_string);
3257
            if ($debug > 0) {
3258
                error_log('Found prereq_string: '.$prereq_string, 0);
3259
            }
3260
3261
            // Now send to the parse_prereq() function that will check this component's prerequisites.
3262
            $result = $currentItem->parse_prereq(
3263
                $prereq_string,
3264
                $this->items,
3265
                $this->refs_list,
3266
                $this->get_user_id()
3267
            );
3268
3269
            if (false === $result) {
3270
                $this->set_error_msg($currentItem->prereq_alert);
3271
            }
3272
        } else {
3273
            $result = true;
3274
            if ($debug > 1) {
3275
                error_log('$this->items['.$itemId.'] was not an object', 0);
3276
            }
3277
        }
3278
3279
        if ($debug > 1) {
3280
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
3281
        }
3282
3283
        return $result;
3284
    }
3285
3286
    /**
3287
     * Updates learnpath attributes to point to the previous element
3288
     * The last part is similar to set_current_item but processing the other way around.
3289
     */
3290
    public function previous()
3291
    {
3292
        $this->last = $this->get_current_item_id();
3293
        $this->items[$this->last]->save(
3294
            false,
3295
            $this->prerequisites_match($this->last)
3296
        );
3297
        $this->autocomplete_parents($this->last);
3298
        $new_index = $this->get_previous_index();
3299
        $this->index = $new_index;
3300
        $this->current = $this->ordered_items[$new_index];
3301
    }
3302
3303
    /**
3304
     * Publishes a learnpath. This basically means show or hide the learnpath
3305
     * to normal users.
3306
     * Can be used as abstract.
3307
     *
3308
     * @param int $id         Learnpath ID
3309
     * @param int $visibility New visibility (1 = visible/published, 0= invisible/draft)
3310
     *
3311
     * @return bool
3312
     */
3313
    public static function toggleVisibility($id, $visibility = 1)
3314
    {
3315
        $repo = Container::getLpRepository();
3316
        $lp = $repo->find($id);
3317
3318
        if (!$lp) {
3319
            return false;
3320
        }
3321
3322
        $visibility = (int) $visibility;
3323
3324
        if (1 === $visibility) {
3325
            $repo->setVisibilityPublished($lp);
3326
        } else {
3327
            $repo->setVisibilityDraft($lp);
3328
        }
3329
3330
        return true;
3331
    }
3332
3333
    /**
3334
     * Publishes a learnpath category.
3335
     * This basically means show or hide the learnpath category to normal users.
3336
     *
3337
     * @param int $id
3338
     * @param int $visibility
3339
     *
3340
     * @return bool
3341
     */
3342
    public static function toggleCategoryVisibility($id, $visibility = 1)
3343
    {
3344
        $repo = Container::getLpCategoryRepository();
3345
        $resource = $repo->find($id);
3346
3347
        if (!$resource) {
3348
            return false;
3349
        }
3350
3351
        $visibility = (int) $visibility;
3352
3353
        if (1 === $visibility) {
3354
            $repo->setVisibilityPublished($resource);
3355
        } else {
3356
            $repo->setVisibilityDraft($resource);
3357
            self::toggleCategoryPublish($id, 0);
3358
        }
3359
3360
        return false;
3361
    }
3362
3363
    /**
3364
     * Publishes a learnpath. This basically means show or hide the learnpath
3365
     * on the course homepage.
3366
     *
3367
     * @param int    $id            Learnpath id
3368
     * @param string $setVisibility New visibility (v/i - visible/invisible)
3369
     *
3370
     * @return bool
3371
     */
3372
    public static function togglePublish($id, $setVisibility = 'v')
3373
    {
3374
        $addShortcut = false;
3375
        if ('v' === $setVisibility) {
3376
            $addShortcut = true;
3377
        }
3378
        $repo = Container::getLpRepository();
3379
        /** @var CLp|null $lp */
3380
        $lp = $repo->find($id);
3381
        if (null === $lp) {
3382
            return false;
3383
        }
3384
        $repoShortcut = Container::getShortcutRepository();
3385
        $courseEntity = api_get_course_entity();
3386
3387
        if ($addShortcut) {
3388
            $repoShortcut->addShortCut($lp, $courseEntity, $courseEntity, api_get_session_entity());
3389
        } else {
3390
            $repoShortcut->removeShortCut($lp);
3391
        }
3392
3393
        return true;
3394
    }
3395
3396
    /**
3397
     * Show or hide the learnpath category on the course homepage.
3398
     *
3399
     * @param int $id
3400
     * @param int $setVisibility
3401
     *
3402
     * @return bool
3403
     */
3404
    public static function toggleCategoryPublish($id, $setVisibility = 1)
3405
    {
3406
        $setVisibility = (int) $setVisibility;
3407
        $addShortcut = false;
3408
        if (1 === $setVisibility) {
3409
            $addShortcut = true;
3410
        }
3411
3412
        $repo = Container::getLpCategoryRepository();
3413
        /** @var CLpCategory|null $lp */
3414
        $category = $repo->find($id);
3415
3416
        if (null === $category) {
3417
            return false;
3418
        }
3419
3420
        $repoShortcut = Container::getShortcutRepository();
3421
        if ($addShortcut) {
3422
            $courseEntity = api_get_course_entity(api_get_course_int_id());
3423
            $repoShortcut->addShortCut($category, $courseEntity, $courseEntity, api_get_session_entity());
3424
        } else {
3425
            $repoShortcut->removeShortCut($category);
3426
        }
3427
3428
        return true;
3429
    }
3430
3431
    /**
3432
     * Check if the learnpath category is visible for a user.
3433
     *
3434
     * @return bool
3435
     */
3436
    public static function categoryIsVisibleForStudent(
3437
        CLpCategory $category,
3438
        User $user,
3439
        Course $course,
3440
        \Chamilo\CoreBundle\Entity\Session $session = null
3441
    ) {
3442
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
3443
3444
        if ($isAllowedToEdit) {
3445
            return true;
3446
        }
3447
3448
        $categoryVisibility = $category->isVisible($course, $session);
3449
3450
        if (!$categoryVisibility) {
3451
            return false;
3452
        }
3453
3454
        $subscriptionSettings = self::getSubscriptionSettings();
3455
3456
        if (false === $subscriptionSettings['allow_add_users_to_lp_category']) {
3457
            return true;
3458
        }
3459
3460
        $noUserSubscribed = false;
3461
        $noGroupSubscribed = true;
3462
        $users = $category->getUsers();
3463
        if (empty($users) || !$users->count()) {
3464
            $noUserSubscribed = true;
3465
        } elseif ($category->hasUserAdded($user)) {
3466
            return true;
3467
        }
3468
3469
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
3470
        $em = Database::getManager();
3471
3472
        /** @var ItemPropertyRepository $itemRepo */
3473
        $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
3474
3475
        /** @var CourseRepository $courseRepo */
3476
        $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
3477
        $session = null;
3478
        if (!empty($sessionId)) {
3479
            $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
3480
        }
3481
3482
        $course = $courseRepo->find($courseId);
3483
3484
        if (0 != $courseId) {
3485
            // Subscribed groups to a LP
3486
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
3487
                    TOOL_LEARNPATH_CATEGORY,
3488
                    $category->getId(),
3489
                    $course,
3490
                    $session
3491
                );
3492
        }
3493
3494
        if (!empty($subscribedGroupsInLp)) {
3495
            $noGroupSubscribed = false;
3496
            if (!empty($groups)) {
3497
                $groups = array_column($groups, 'iid');
3498
                /** @var CItemProperty $item */
3499
                foreach ($subscribedGroupsInLp as $item) {
3500
                    if ($item->getGroup() &&
3501
                        in_array($item->getGroup()->getId(), $groups)
3502
                    ) {
3503
                        return true;
3504
                    }
3505
                }
3506
            }
3507
        }
3508
        $response = $noGroupSubscribed && $noUserSubscribed;
3509
3510
        return $response;
3511
    }
3512
3513
    /**
3514
     * Check if a learnpath category is published as course tool.
3515
     *
3516
     * @param int $courseId
3517
     *
3518
     * @return bool
3519
     */
3520
    public static function categoryIsPublished(CLpCategory $category, $courseId)
3521
    {
3522
        return false;
3523
        $link = self::getCategoryLinkForTool($category->getId());
0 ignored issues
show
Unused Code introduced by
$link = self::getCategor...ool($category->getId()) is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
3524
        $em = Database::getManager();
3525
3526
        $tools = $em
3527
            ->createQuery("
3528
                SELECT t FROM ChamiloCourseBundle:CTool t
3529
                WHERE t.course = :course AND
3530
                    t.name = :name AND
3531
                    t.image LIKE 'lp_category.%' AND
3532
                    t.link LIKE :link
3533
            ")
3534
            ->setParameters([
3535
                'course' => $courseId,
3536
                'name' => strip_tags($category->getName()),
3537
                'link' => "$link%",
3538
            ])
3539
            ->getResult();
3540
3541
        /** @var CTool $tool */
3542
        $tool = current($tools);
3543
3544
        return $tool ? $tool->getVisibility() : false;
3545
    }
3546
3547
    /**
3548
     * Restart the whole learnpath. Return the URL of the first element.
3549
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
3550
     * To use a similar method  statically, use the create_new_attempt() method.
3551
     *
3552
     * @return bool
3553
     */
3554
    public function restart()
3555
    {
3556
        if ($this->debug > 0) {
3557
            error_log('In learnpath::restart()', 0);
3558
        }
3559
        // TODO
3560
        // Call autosave method to save the current progress.
3561
        //$this->index = 0;
3562
        if (api_is_invitee()) {
3563
            return false;
3564
        }
3565
        $session_id = api_get_session_id();
3566
        $course_id = api_get_course_int_id();
3567
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3568
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
3569
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
3570
        if ($this->debug > 2) {
3571
            error_log('Inserting new lp_view for restart: '.$sql, 0);
3572
        }
3573
        Database::query($sql);
3574
        $view_id = Database::insert_id();
3575
3576
        if ($view_id) {
3577
            $this->lp_view_id = $view_id;
3578
            $this->attempt = $this->attempt + 1;
3579
        } else {
3580
            $this->error = 'Could not insert into item_view table...';
3581
3582
            return false;
3583
        }
3584
        $this->autocomplete_parents($this->current);
3585
        foreach ($this->items as $index => $dummy) {
3586
            $this->items[$index]->restart();
3587
            $this->items[$index]->set_lp_view($this->lp_view_id);
3588
        }
3589
        $this->first();
3590
3591
        return true;
3592
    }
3593
3594
    /**
3595
     * Saves the current item.
3596
     *
3597
     * @return bool
3598
     */
3599
    public function save_current()
3600
    {
3601
        $debug = $this->debug;
3602
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
3603
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
3604
        if ($debug) {
3605
            error_log('save_current() saving item '.$this->current, 0);
3606
            error_log(''.print_r($this->items, true), 0);
3607
        }
3608
        if (isset($this->items[$this->current]) &&
3609
            is_object($this->items[$this->current])
3610
        ) {
3611
            if ($debug) {
3612
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
3613
            }
3614
3615
            $res = $this->items[$this->current]->save(
3616
                false,
3617
                $this->prerequisites_match($this->current)
3618
            );
3619
            $this->autocomplete_parents($this->current);
3620
            $status = $this->items[$this->current]->get_status();
3621
            $this->update_queue[$this->current] = $status;
3622
3623
            if ($debug) {
3624
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
3625
            }
3626
3627
            return $res;
3628
        }
3629
3630
        return false;
3631
    }
3632
3633
    /**
3634
     * Saves the given item.
3635
     *
3636
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
3637
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
3638
     *
3639
     * @return bool
3640
     */
3641
    public function save_item($item_id = null, $from_outside = true)
3642
    {
3643
        $debug = $this->debug;
3644
        if ($debug) {
3645
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
3646
        }
3647
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
3648
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
3649
        if (empty($item_id)) {
3650
            $item_id = (int) $_REQUEST['id'];
3651
        }
3652
3653
        if (empty($item_id)) {
3654
            $item_id = $this->get_current_item_id();
3655
        }
3656
        if (isset($this->items[$item_id]) &&
3657
            is_object($this->items[$item_id])
3658
        ) {
3659
            if ($debug) {
3660
                error_log('Object exists');
3661
            }
3662
3663
            // Saving the item.
3664
            $res = $this->items[$item_id]->save(
3665
                $from_outside,
3666
                $this->prerequisites_match($item_id)
3667
            );
3668
3669
            if ($debug) {
3670
                error_log('update_queue before:');
3671
                error_log(print_r($this->update_queue, 1));
3672
            }
3673
            $this->autocomplete_parents($item_id);
3674
3675
            $status = $this->items[$item_id]->get_status();
3676
            $this->update_queue[$item_id] = $status;
3677
3678
            if ($debug) {
3679
                error_log('get_status(): '.$status);
3680
                error_log('update_queue after:');
3681
                error_log(print_r($this->update_queue, 1));
3682
            }
3683
3684
            return $res;
3685
        }
3686
3687
        return false;
3688
    }
3689
3690
    /**
3691
     * Saves the last item seen's ID only in case.
3692
     */
3693
    public function save_last()
3694
    {
3695
        $course_id = api_get_course_int_id();
3696
        $debug = $this->debug;
3697
        if ($debug) {
3698
            error_log('In learnpath::save_last()', 0);
3699
        }
3700
        $session_condition = api_get_session_condition(
3701
            api_get_session_id(),
3702
            true,
3703
            false
3704
        );
3705
        $table = Database::get_course_table(TABLE_LP_VIEW);
3706
3707
        $userId = $this->get_user_id();
3708
        if (empty($userId)) {
3709
            $userId = api_get_user_id();
3710
            if ($debug) {
3711
                error_log('$this->get_user_id() was empty, used api_get_user_id() instead in '.__FILE__.' line '.__LINE__);
3712
            }
3713
        }
3714
        if (isset($this->current) && !api_is_invitee()) {
3715
            if ($debug) {
3716
                error_log('Saving current item ('.$this->current.') for later review', 0);
3717
            }
3718
            $sql = "UPDATE $table SET
3719
                        last_item = ".$this->get_current_item_id()."
3720
                    WHERE
3721
                        c_id = $course_id AND
3722
                        lp_id = ".$this->get_id()." AND
3723
                        user_id = ".$userId." ".$session_condition;
3724
3725
            if ($debug) {
3726
                error_log('Saving last item seen : '.$sql, 0);
3727
            }
3728
            Database::query($sql);
3729
        }
3730
3731
        if (!api_is_invitee()) {
3732
            // Save progress.
3733
            [$progress] = $this->get_progress_bar_text('%');
3734
            $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
3735
            $scoreAsProgress = $this->getUseScoreAsProgress();
3736
            if ($scoreAsProgress && $scoreAsProgressSetting && (null === $score || empty($score) || -1 == $score)) {
3737
                if ($debug) {
3738
                    error_log("Return false: Dont save score: $score");
3739
                    error_log("progress: $progress");
3740
                }
3741
3742
                return false;
3743
            }
3744
3745
            if ($scoreAsProgress && $scoreAsProgressSetting) {
3746
                $storedProgress = self::getProgress(
3747
                    $this->get_id(),
3748
                    $userId,
3749
                    $course_id,
3750
                    $this->get_lp_session_id()
3751
                );
3752
3753
                // Check if the stored progress is higher than the new value
3754
                if ($storedProgress >= $progress) {
3755
                    if ($debug) {
3756
                        error_log("Return false: New progress value is lower than stored value - Current value: $storedProgress - New value: $progress [lp ".$this->get_id()." - user ".$userId."]");
3757
                    }
3758
3759
                    return false;
3760
                }
3761
            }
3762
            if ($progress >= 0 && $progress <= 100) {
3763
                $progress = (int) $progress;
3764
                $sql = "UPDATE $table SET
3765
                            progress = $progress
3766
                        WHERE
3767
                            c_id = $course_id AND
3768
                            lp_id = ".$this->get_id()." AND
3769
                            user_id = ".$userId." ".$session_condition;
3770
                // Ignore errors as some tables might not have the progress field just yet.
3771
                Database::query($sql);
3772
                $this->progress_db = $progress;
3773
            }
3774
        }
3775
    }
3776
3777
    /**
3778
     * Sets the current item ID (checks if valid and authorized first).
3779
     *
3780
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
3781
     */
3782
    public function set_current_item($item_id = null)
3783
    {
3784
        $debug = $this->debug;
3785
        if ($debug) {
3786
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
3787
        }
3788
        if (empty($item_id)) {
3789
            if ($debug) {
3790
                error_log('No new current item given, ignore...', 0);
3791
            }
3792
            // Do nothing.
3793
        } else {
3794
            if ($debug) {
3795
                error_log('New current item given is '.$item_id.'...', 0);
3796
            }
3797
            if (is_numeric($item_id)) {
3798
                $item_id = (int) $item_id;
3799
                // TODO: Check in database here.
3800
                $this->last = $this->current;
3801
                $this->current = $item_id;
3802
                // TODO: Update $this->index as well.
3803
                foreach ($this->ordered_items as $index => $item) {
3804
                    if ($item == $this->current) {
3805
                        $this->index = $index;
3806
                        break;
3807
                    }
3808
                }
3809
                if ($debug) {
3810
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
3811
                }
3812
            } else {
3813
                if ($debug) {
3814
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
3815
                }
3816
            }
3817
        }
3818
    }
3819
3820
    /**
3821
     * Set index specified prefix terms for all items in this path.
3822
     *
3823
     * @param string $terms_string Comma-separated list of terms
3824
     * @param string $prefix       Xapian term prefix
3825
     *
3826
     * @return bool False on error, true otherwise
3827
     */
3828
    public function set_terms_by_prefix($terms_string, $prefix)
3829
    {
3830
        $course_id = api_get_course_int_id();
3831
        if ('true' !== api_get_setting('search_enabled')) {
3832
            return false;
3833
        }
3834
3835
        if (!extension_loaded('xapian')) {
3836
            return false;
3837
        }
3838
3839
        $terms_string = trim($terms_string);
3840
        $terms = explode(',', $terms_string);
3841
        array_walk($terms, 'trim_value');
3842
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
3843
3844
        // Don't do anything if no change, verify only at DB, not the search engine.
3845
        if ((0 == count(array_diff($terms, $stored_terms))) && (0 == count(array_diff($stored_terms, $terms)))) {
3846
            return false;
3847
        }
3848
3849
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
3850
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
3851
3852
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
3853
        // TODO: Make query secure agains XSS : use member attr instead of post var.
3854
        $lp_id = (int) $_POST['lp_id'];
3855
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
3856
        $result = Database::query($sql);
3857
        $di = new ChamiloIndexer();
3858
3859
        while ($lp_item = Database::fetch_array($result)) {
3860
            // Get search_did.
3861
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
3862
            $sql = 'SELECT * FROM %s
3863
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
3864
                    LIMIT 1';
3865
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
3866
3867
            //echo $sql; echo '<br>';
3868
            $res = Database::query($sql);
3869
            if (Database::num_rows($res) > 0) {
3870
                $se_ref = Database::fetch_array($res);
3871
                // Compare terms.
3872
                $doc = $di->get_document($se_ref['search_did']);
3873
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
3874
                $xterms = [];
3875
                foreach ($xapian_terms as $xapian_term) {
3876
                    $xterms[] = substr($xapian_term['name'], 1);
3877
                }
3878
3879
                $dterms = $terms;
3880
                $missing_terms = array_diff($dterms, $xterms);
3881
                $deprecated_terms = array_diff($xterms, $dterms);
3882
3883
                // Save it to search engine.
3884
                foreach ($missing_terms as $term) {
3885
                    $doc->add_term($prefix.$term, 1);
3886
                }
3887
                foreach ($deprecated_terms as $term) {
3888
                    $doc->remove_term($prefix.$term);
3889
                }
3890
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
3891
                $di->getDb()->flush();
3892
            }
3893
        }
3894
3895
        return true;
3896
    }
3897
3898
    /**
3899
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
3900
     *
3901
     * @param int $id DB ID of the item
3902
     */
3903
    public function set_previous_item($id)
3904
    {
3905
        if ($this->debug > 0) {
3906
            error_log('In learnpath::set_previous_item()', 0);
3907
        }
3908
        $this->last = $id;
3909
    }
3910
3911
    /**
3912
     * Sets and saves the expired_on date.
3913
     *
3914
     * @return bool Returns true if author's name is not empty
3915
     */
3916
    public function set_modified_on()
3917
    {
3918
        $this->modified_on = api_get_utc_datetime();
3919
        $table = Database::get_course_table(TABLE_LP_MAIN);
3920
        $lp_id = $this->get_id();
3921
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
3922
                WHERE iid = $lp_id";
3923
        Database::query($sql);
3924
3925
        return true;
3926
    }
3927
3928
    /**
3929
     * Sets the object's error message.
3930
     *
3931
     * @param string $error Error message. If empty, reinits the error string
3932
     */
3933
    public function set_error_msg($error = '')
3934
    {
3935
        if ($this->debug > 0) {
3936
            error_log('In learnpath::set_error_msg()', 0);
3937
        }
3938
        if (empty($error)) {
3939
            $this->error = '';
3940
        } else {
3941
            $this->error .= $error;
3942
        }
3943
    }
3944
3945
    /**
3946
     * Launches the current item if not 'sco'
3947
     * (starts timer and make sure there is a record ready in the DB).
3948
     *
3949
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
3950
     *
3951
     * @return bool
3952
     */
3953
    public function start_current_item($allow_new_attempt = false)
3954
    {
3955
        $debug = $this->debug;
3956
        if ($debug) {
3957
            error_log('In learnpath::start_current_item()');
3958
            error_log('current: '.$this->current);
3959
        }
3960
        if (0 != $this->current && isset($this->items[$this->current]) &&
3961
            is_object($this->items[$this->current])
3962
        ) {
3963
            $type = $this->get_type();
3964
            $item_type = $this->items[$this->current]->get_type();
3965
            if ($debug) {
3966
                error_log('item type: '.$item_type);
3967
                error_log('lp type: '.$type);
3968
            }
3969
            if ((2 == $type && 'sco' !== $item_type) ||
3970
                (3 == $type && 'au' !== $item_type) ||
3971
                (1 == $type && TOOL_QUIZ != $item_type && TOOL_HOTPOTATOES != $item_type)
3972
            ) {
3973
                $this->items[$this->current]->open($allow_new_attempt);
3974
                $this->autocomplete_parents($this->current);
3975
                $prereq_check = $this->prerequisites_match($this->current);
3976
                if ($debug) {
3977
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
3978
                }
3979
                $this->items[$this->current]->save(false, $prereq_check);
3980
            }
3981
            // If sco, then it is supposed to have been updated by some other call.
3982
            if ('sco' === $item_type) {
3983
                $this->items[$this->current]->restart();
3984
            }
3985
        }
3986
        if ($debug) {
3987
            error_log('lp_view_session_id');
3988
            error_log($this->lp_view_session_id);
3989
            error_log('api session id');
3990
            error_log(api_get_session_id());
3991
            error_log('End of learnpath::start_current_item()');
3992
        }
3993
3994
        return true;
3995
    }
3996
3997
    /**
3998
     * Stops the processing and counters for the old item (as held in $this->last).
3999
     *
4000
     * @return bool True/False
4001
     */
4002
    public function stop_previous_item()
4003
    {
4004
        $debug = $this->debug;
4005
        if ($debug) {
4006
            error_log('In learnpath::stop_previous_item()', 0);
4007
        }
4008
4009
        if (0 != $this->last && $this->last != $this->current &&
4010
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
4011
        ) {
4012
            if ($debug) {
4013
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
4014
            }
4015
            switch ($this->get_type()) {
4016
                case '3':
4017
                    if ('au' != $this->items[$this->last]->get_type()) {
4018
                        if ($debug) {
4019
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
4020
                        }
4021
                        $this->items[$this->last]->close();
4022
                    } else {
4023
                        if ($debug) {
4024
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
4025
                        }
4026
                    }
4027
                    break;
4028
                case '2':
4029
                    if ('sco' != $this->items[$this->last]->get_type()) {
4030
                        if ($debug) {
4031
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
4032
                        }
4033
                        $this->items[$this->last]->close();
4034
                    } else {
4035
                        if ($debug) {
4036
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
4037
                        }
4038
                    }
4039
                    break;
4040
                case '1':
4041
                default:
4042
                    if ($debug) {
4043
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
4044
                    }
4045
                    $this->items[$this->last]->close();
4046
                    break;
4047
            }
4048
        } else {
4049
            if ($debug) {
4050
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
4051
            }
4052
4053
            return false;
4054
        }
4055
4056
        return true;
4057
    }
4058
4059
    /**
4060
     * Updates the default view mode from fullscreen to embedded and inversely.
4061
     *
4062
     * @return string The current default view mode ('fullscreen' or 'embedded')
4063
     */
4064
    public function update_default_view_mode()
4065
    {
4066
        $table = Database::get_course_table(TABLE_LP_MAIN);
4067
        $sql = "SELECT * FROM $table
4068
                WHERE iid = ".$this->get_id();
4069
        $res = Database::query($sql);
4070
        if (Database::num_rows($res) > 0) {
4071
            $row = Database::fetch_array($res);
4072
            $default_view_mode = $row['default_view_mod'];
4073
            $view_mode = $default_view_mode;
4074
            switch ($default_view_mode) {
4075
                case 'fullscreen': // default with popup
4076
                    $view_mode = 'embedded';
4077
                    break;
4078
                case 'embedded': // default view with left menu
4079
                    $view_mode = 'embedframe';
4080
                    break;
4081
                case 'embedframe': //folded menu
4082
                    $view_mode = 'impress';
4083
                    break;
4084
                case 'impress':
4085
                    $view_mode = 'fullscreen';
4086
                    break;
4087
            }
4088
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
4089
                    WHERE iid = ".$this->get_id();
4090
            Database::query($sql);
4091
            $this->mode = $view_mode;
4092
4093
            return $view_mode;
4094
        }
4095
4096
        return -1;
4097
    }
4098
4099
    /**
4100
     * Updates the default behaviour about auto-commiting SCORM updates.
4101
     *
4102
     * @return bool True if auto-commit has been set to 'on', false otherwise
4103
     */
4104
    public function update_default_scorm_commit()
4105
    {
4106
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4107
        $sql = "SELECT * FROM $lp_table
4108
                WHERE iid = ".$this->get_id();
4109
        $res = Database::query($sql);
4110
        if (Database::num_rows($res) > 0) {
4111
            $row = Database::fetch_array($res);
4112
            $force = $row['force_commit'];
4113
            if (1 == $force) {
4114
                $force = 0;
4115
                $force_return = false;
4116
            } elseif (0 == $force) {
4117
                $force = 1;
4118
                $force_return = true;
4119
            }
4120
            $sql = "UPDATE $lp_table SET force_commit = $force
4121
                    WHERE iid = ".$this->get_id();
4122
            Database::query($sql);
4123
            $this->force_commit = $force_return;
4124
4125
            return $force_return;
4126
        }
4127
4128
        return -1;
4129
    }
4130
4131
    /**
4132
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
4133
     *
4134
     * @return bool True on success, false on failure
4135
     */
4136
    public function update_display_order()
4137
    {
4138
        return;
4139
        $course_id = api_get_course_int_id();
0 ignored issues
show
Unused Code introduced by
$course_id = api_get_course_int_id() is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
4140
        $table = Database::get_course_table(TABLE_LP_MAIN);
4141
        $sql = "SELECT * FROM $table
4142
                WHERE c_id = $course_id
4143
                ORDER BY display_order";
4144
        $res = Database::query($sql);
4145
        if (false === $res) {
4146
            return false;
4147
        }
4148
4149
        $num = Database::num_rows($res);
4150
        // First check the order is correct, globally (might be wrong because
4151
        // of versions < 1.8.4).
4152
        if ($num > 0) {
4153
            $i = 1;
4154
            while ($row = Database::fetch_array($res)) {
4155
                if ($row['display_order'] != $i) {
4156
                    // If we find a gap in the order, we need to fix it.
4157
                    $sql = "UPDATE $table SET display_order = $i
4158
                            WHERE iid = ".$row['iid'];
4159
                    Database::query($sql);
4160
                }
4161
                $i++;
4162
            }
4163
        }
4164
4165
        return true;
4166
    }
4167
4168
    /**
4169
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
4170
     *
4171
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
4172
     */
4173
    public function update_reinit()
4174
    {
4175
        $force = $this->entity->getPreventReinit();
4176
        if (1 == $force) {
4177
            $force = 0;
4178
        } elseif (0 == $force) {
4179
            $force = 1;
4180
        }
4181
4182
        $table = Database::get_course_table(TABLE_LP_MAIN);
4183
        $sql = "UPDATE $table SET prevent_reinit = $force
4184
                WHERE iid = ".$this->get_id();
4185
        Database::query($sql);
4186
        $this->prevent_reinit = $force;
4187
4188
        return $force;
4189
    }
4190
4191
    /**
4192
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
4193
     *
4194
     * @return string 'single', 'multi' or 'seriousgame'
4195
     *
4196
     * @author ndiechburg <[email protected]>
4197
     */
4198
    public function get_attempt_mode()
4199
    {
4200
        //Set default value for seriousgame_mode
4201
        if (!isset($this->seriousgame_mode)) {
4202
            $this->seriousgame_mode = 0;
4203
        }
4204
        // Set default value for prevent_reinit
4205
        if (!isset($this->prevent_reinit)) {
4206
            $this->prevent_reinit = 1;
4207
        }
4208
        if (1 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
4209
            return 'seriousgame';
4210
        }
4211
        if (0 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
4212
            return 'single';
4213
        }
4214
        if (0 == $this->seriousgame_mode && 0 == $this->prevent_reinit) {
4215
            return 'multiple';
4216
        }
4217
4218
        return 'single';
4219
    }
4220
4221
    /**
4222
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
4223
     *
4224
     * @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...
4225
     *
4226
     * @return bool
4227
     *
4228
     * @author ndiechburg <[email protected]>
4229
     */
4230
    public function set_attempt_mode($mode)
4231
    {
4232
        switch ($mode) {
4233
            case 'seriousgame':
4234
                $sg_mode = 1;
4235
                $prevent_reinit = 1;
4236
                break;
4237
            case 'single':
4238
                $sg_mode = 0;
4239
                $prevent_reinit = 1;
4240
                break;
4241
            case 'multiple':
4242
                $sg_mode = 0;
4243
                $prevent_reinit = 0;
4244
                break;
4245
            default:
4246
                $sg_mode = 0;
4247
                $prevent_reinit = 0;
4248
                break;
4249
        }
4250
        $this->prevent_reinit = $prevent_reinit;
4251
        $this->seriousgame_mode = $sg_mode;
4252
        $table = Database::get_course_table(TABLE_LP_MAIN);
4253
        $sql = "UPDATE $table SET
4254
                prevent_reinit = $prevent_reinit ,
4255
                seriousgame_mode = $sg_mode
4256
                WHERE iid = ".$this->get_id();
4257
        $res = Database::query($sql);
4258
        if ($res) {
0 ignored issues
show
introduced by
$res is of type Doctrine\DBAL\Driver\Statement, thus it always evaluated to true.
Loading history...
4259
            return true;
4260
        } else {
4261
            return false;
4262
        }
4263
    }
4264
4265
    /**
4266
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
4267
     *
4268
     * @author ndiechburg <[email protected]>
4269
     */
4270
    public function switch_attempt_mode()
4271
    {
4272
        $mode = $this->get_attempt_mode();
4273
        switch ($mode) {
4274
            case 'single':
4275
                $next_mode = 'multiple';
4276
                break;
4277
            case 'multiple':
4278
                $next_mode = 'seriousgame';
4279
                break;
4280
            case 'seriousgame':
4281
            default:
4282
                $next_mode = 'single';
4283
                break;
4284
        }
4285
        $this->set_attempt_mode($next_mode);
4286
    }
4287
4288
    /**
4289
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
4290
     * but possibility to do again a completed item.
4291
     *
4292
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
4293
     *
4294
     * @author ndiechburg <[email protected]>
4295
     */
4296
    public function set_seriousgame_mode()
4297
    {
4298
        $table = Database::get_course_table(TABLE_LP_MAIN);
4299
        $force = $this->entity->getSeriousgameMode();
4300
        if (1 == $force) {
4301
            $force = 0;
4302
        } elseif (0 == $force) {
4303
            $force = 1;
4304
        }
4305
        $sql = "UPDATE $table SET seriousgame_mode = $force
4306
                WHERE iid = ".$this->get_id();
4307
        Database::query($sql);
4308
        $this->seriousgame_mode = $force;
4309
4310
        return $force;
4311
    }
4312
4313
    /**
4314
     * Updates the "scorm_debug" value that shows or hide the debug window.
4315
     *
4316
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
4317
     */
4318
    public function update_scorm_debug()
4319
    {
4320
        $table = Database::get_course_table(TABLE_LP_MAIN);
4321
        $force = $this->entity->getDebug();
4322
        if (1 == $force) {
4323
            $force = 0;
4324
        } elseif (0 == $force) {
4325
            $force = 1;
4326
        }
4327
        $sql = "UPDATE $table SET debug = $force
4328
                WHERE iid = ".$this->get_id();
4329
        Database::query($sql);
4330
        $this->scorm_debug = $force;
4331
4332
        return $force;
4333
    }
4334
4335
    /**
4336
     * Function that creates a html list of learning path items so that we can add audio files to them.
4337
     *
4338
     * @author Kevin Van Den Haute
4339
     *
4340
     * @return string
4341
     */
4342
    public function overview()
4343
    {
4344
        $return = '';
4345
        $update_audio = $_GET['updateaudio'] ?? null;
4346
4347
        // we need to start a form when we want to update all the mp3 files
4348
        if ('true' == $update_audio) {
4349
            $return .= '<form action="'.api_get_self().'?'.api_get_cidreq().'&updateaudio='.Security::remove_XSS(
4350
                    $_GET['updateaudio']
4351
                ).'&action='.Security::remove_XSS(
4352
                    $_GET['action']
4353
                ).'&lp_id='.$_SESSION['oLP']->lp_id.'" method="post" enctype="multipart/form-data" name="updatemp3" id="updatemp3">';
4354
        }
4355
        $return .= '<div id="message"></div>';
4356
        if (0 == count($this->items)) {
4357
            $return .= Display::return_message(
4358
                get_lang(
4359
                    'You should add some items to your learning path, otherwise you won\'t be able to attach audio files to them'
4360
                ),
4361
                'normal'
4362
            );
4363
        } else {
4364
            $return_audio = '<table class="table table-hover table-striped data_table">';
4365
            $return_audio .= '<tr>';
4366
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
4367
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
4368
            $return_audio .= '</tr>';
4369
4370
            if ('true' != $update_audio) {
4371
                /*$return .= '<div class="col-md-12">';
4372
                $return .= self::return_new_tree($update_audio);
4373
                $return .= '</div>';*/
4374
                $return .= Display::div(
4375
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
4376
                    ['style' => 'float:left; margin-top:15px;width:100%']
4377
                );
4378
            } else {
4379
                //$return_audio .= self::return_new_tree($update_audio);
4380
                $return .= $return_audio.'</table>';
4381
            }
4382
4383
            // We need to close the form when we are updating the mp3 files.
4384
            if ('true' == $update_audio) {
4385
                $return .= '<div class="footer-audio">';
4386
                $return .= Display::button(
4387
                    'save_audio',
4388
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('Save audio and organization'),
4389
                    ['class' => 'btn btn-primary', 'type' => 'submit']
4390
                );
4391
                $return .= '</div>';
4392
            }
4393
        }
4394
4395
        // We need to close the form when we are updating the mp3 files.
4396
        if ('true' === $update_audio && isset($this->arrMenu) && 0 != count($this->arrMenu)) {
4397
            $return .= '</form>';
4398
        }
4399
4400
        return $return;
4401
    }
4402
4403
    public function showBuildSideBar($updateAudio = false, $dropElementHere = false, $type = null)
4404
    {
4405
        $sureToDelete = trim(get_lang('Are you sure to delete?'));
4406
        $ajax_url = api_get_path(WEB_AJAX_PATH).'lp.ajax.php?lp_id='.$this->get_id().'&'.api_get_cidreq();
4407
4408
        $content = '
4409
        <script>
4410
            /*
4411
            Script to manipulate Learning Path items with Drag and drop
4412
             */
4413
            $(function() {
4414
                function refreshTree() {
4415
                    var params = "&a=get_lp_item_tree";
4416
                    $.get(
4417
                        "'.$ajax_url.'",
4418
                        params,
4419
                        function(result) {
4420
                            serialized = [];
4421
                            $("#lp_item_list").html(result);
4422
                            nestedSortable();
4423
                        }
4424
                    );
4425
                }
4426
4427
                const nestedQuery = ".nested-sortable";
4428
                const identifier = "id";
4429
                const root = document.getElementById("lp_item_list");
4430
4431
                var serialized = [];
4432
                function serialize(sortable) {
4433
                  var children = [].slice.call(sortable.children);
4434
                  for (var i in children) {
4435
                    var nested = children[i].querySelector(nestedQuery);
4436
                    var parentId = $(children[i]).parent().parent().attr("id");
4437
                    var id = children[i].dataset[identifier];
4438
                    if (typeof id === "undefined") {
4439
                        return;
4440
                    }
4441
                    serialized.push({
4442
                      id: children[i].dataset[identifier],
4443
                      parent_id: parentId
4444
                    });
4445
4446
                    if (nested) {
4447
                        serialize(nested);
4448
                    }
4449
                  }
4450
4451
                  return serialized;
4452
                }
4453
4454
                function nestedSortable() {
4455
                    let left = document.getElementsByClassName("nested-sortable");
4456
                    Array.prototype.forEach.call(left, function(resource) {
4457
                        Sortable.create(resource, {
4458
                            group: "nested",
4459
                            put: ["nested-sortable", ".lp_resource", ".nested-source"],
4460
                            animation: 150,
4461
                            //fallbackOnBody: true,
4462
                            swapThreshold: 0.65,
4463
                            dataIdAttr: "data-id",
4464
                            store: {
4465
                                set: function (sortable) {
4466
                                    var order = sortable.toArray();
4467
                                    console.log(order);
4468
                                }
4469
                            },
4470
                            onEnd: function(evt) {
4471
                                console.log("onEnd");
4472
                                let list = serialize(root);
4473
                                let order = "&a=update_lp_item_order&new_order=" + JSON.stringify(list);
4474
                                $.get(
4475
                                    "'.$ajax_url.'",
4476
                                    order,
4477
                                    function(reponse) {
4478
                                        $("#message").html(reponse);
4479
                                        refreshTree();
4480
                                    }
4481
                                );
4482
                            },
4483
                        });
4484
                    });
4485
                }
4486
4487
                nestedSortable();
4488
4489
                let resources = document.getElementsByClassName("lp_resource");
4490
                Array.prototype.forEach.call(resources, function(resource) {
4491
                    Sortable.create(resource, {
4492
                        group: "nested",
4493
                        put: ["nested-sortable"],
4494
                        filter: ".disable_drag",
4495
                        animation: 150,
4496
                        fallbackOnBody: true,
4497
                        swapThreshold: 0.65,
4498
                        dataIdAttr: "data-id",
4499
                        onRemove: function(evt) {
4500
                            console.log("onRemove");
4501
                            var itemEl = evt.item;
4502
                            var newIndex = evt.newIndex;
4503
                            var id = $(itemEl).attr("id");
4504
                            var parent_id = $(itemEl).parent().parent().attr("id");
4505
                            var type =  $(itemEl).find(".link_with_id").attr("data_type");
4506
                            var title = $(itemEl).find(".link_with_id").text();
4507
4508
                            let previousId = 0;
4509
                            if (0 !== newIndex) {
4510
                                previousId = $(itemEl).prev().attr("id");
4511
                            }
4512
                            var params = {
4513
                                "a": "add_lp_item",
4514
                                "id": id,
4515
                                "parent_id": parent_id,
4516
                                "previous_id": previousId,
4517
                                "type": type,
4518
                                "title" : title
4519
                            };
4520
                            console.log(params);
4521
                            $.ajax({
4522
                                type: "GET",
4523
                                url: "'.$ajax_url.'",
4524
                                data: params,
4525
                                success: function(itemId) {
4526
                                    $(itemEl).attr("id", itemId);
4527
                                    $(itemEl).attr("data-id", itemId);
4528
                                    let list = serialize(root);
4529
                                    let listInString = JSON.stringify(list);
4530
                                    if (typeof listInString === "undefined") {
4531
                                        listInString = "";
4532
                                    }
4533
                                    let order = "&a=update_lp_item_order&new_order=" + listInString;
4534
                                    $.get(
4535
                                        "'.$ajax_url.'",
4536
                                        order,
4537
                                        function(reponse) {
4538
                                            $("#message").html(reponse);
4539
                                            refreshTree();
4540
                                        }
4541
                                    );
4542
                                }
4543
                            });
4544
                        },
4545
                    });
4546
                });
4547
            });
4548
        </script>';
4549
4550
        $content .= "
4551
        <script>
4552
            function confirmation(name) {
4553
                if (confirm('$sureToDelete ' + name)) {
4554
                    return true;
4555
                } else {
4556
                    return false;
4557
                }
4558
            }
4559
            function refreshTree() {
4560
                var params = '&a=get_lp_item_tree';
4561
                $.get(
4562
                    '".$ajax_url."',
4563
                    params,
4564
                    function(result) {
4565
                        $('#lp_item_list').html(result);
4566
                    }
4567
                );
4568
            }
4569
4570
            $(function () {
4571
                //$('.scrollbar-inner').scrollbar();
4572
                /*$('#subtab').on('click', 'a:first', function() {
4573
                    window.location.reload();
4574
                });
4575
                $('#subtab ').on('click', 'a:first', function () {
4576
                    window.location.reload();
4577
                });*/
4578
                
4579
                expandColumnToggle('#hide_bar_template', {
4580
                    selector: '#lp_sidebar'
4581
                }, {
4582
                    selector: '#doc_form'
4583
                });
4584
4585
                $('.lp-btn-associate-forum').on('click', function (e) {
4586
                    var associate = confirm('".get_lang('ConfirmAssociateForumToLPItem')."');
4587
                    if (!associate) {
4588
                        e.preventDefault();
4589
                    }
4590
                });
4591
4592
                $('.lp-btn-dissociate-forum').on('click', function (e) {
4593
                    var dissociate = confirm('".get_lang('ConfirmDissociateForumToLPItem')."');
4594
                    if (!dissociate) {
4595
                        e.preventDefault();
4596
                    }
4597
                });
4598
4599
                // hide the current template list for new documment until it tab clicked
4600
                $('#frmModel').hide();
4601
            });
4602
4603
            // document template for new document tab handler
4604
            /*$(document).on('shown.bs.tab', 'a[data-toggle=\"tab\"]', function (e) {
4605
                var id = e.target.id;
4606
                if (id == 'subtab2') {
4607
                    $('#frmModel').show();
4608
                } else {
4609
                    $('#frmModel').hide();
4610
                }
4611
            });*/
4612
4613
          function deleteItem(event) {
4614
            var id = $(event).attr('data-id');
4615
            var title = $(event).attr('data-title');
4616
            var params = '&a=delete_item&id=' + id;
4617
            if (confirmation(title)) {
4618
                $.get(
4619
                    '".$ajax_url."',
4620
                    params,
4621
                    function(result) {
4622
                        refreshTree();
4623
                    }
4624
                );
4625
            }
4626
        }
4627
        </script>";
4628
4629
        $content .= $this->return_new_tree($updateAudio, $dropElementHere);
4630
        $documentId = isset($_GET['path_item']) ? (int) $_GET['path_item'] : 0;
4631
4632
        $repo = Container::getDocumentRepository();
4633
        $document = $repo->find($documentId);
4634
        if ($document) {
4635
            // Show the template list
4636
            $content .= '<div id="frmModel" class="scrollbar-inner lp-add-item"></div>';
4637
        }
4638
4639
        // Show the template list.
4640
        if (('document' === $type || 'step' === $type) && !isset($_GET['file'])) {
4641
            // Show the template list.
4642
            $content .= '<div id="frmModel" class="scrollbar-inner lp-add-item"></div>';
4643
        }
4644
4645
        return $content;
4646
    }
4647
4648
    /**
4649
     * @param bool  $updateAudio
4650
     * @param bool   $dropElement
4651
     *
4652
     * @return string
4653
     */
4654
    public function return_new_tree($updateAudio = false, $dropElement = false)
4655
    {
4656
        $list = $this->getBuildTree(false, $dropElement);
4657
        $return = Display::panelCollapse(
4658
            $this->name,
4659
            $list,
4660
            'scorm-list',
4661
            null,
4662
            'scorm-list-accordion',
4663
            'scorm-list-collapse'
4664
        );
4665
4666
        if ($updateAudio) {
4667
            //$return = $result['return_audio'];
4668
        }
4669
4670
        return $return;
4671
    }
4672
4673
    public function getBuildTree($noWrapper = false, $dropElement = false): string
4674
    {
4675
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
4676
4677
        $upIcon = Display::return_icon(
4678
            'up.png',
4679
            get_lang('Up'),
4680
            [],
4681
            ICON_SIZE_TINY
4682
        );
4683
4684
        $disableUpIcon = Display::return_icon(
4685
            'up_na.png',
4686
            get_lang('Up'),
4687
            [],
4688
            ICON_SIZE_TINY
4689
        );
4690
4691
        $downIcon = Display::return_icon(
4692
            'down.png',
4693
            get_lang('Down'),
4694
            [],
4695
            ICON_SIZE_TINY
4696
        );
4697
4698
        $previewImage = Display::return_icon(
4699
            'preview_view.png',
4700
            get_lang('Preview'),
4701
            [],
4702
            ICON_SIZE_TINY
4703
        );
4704
        $lpItemRepo = Container::getLpItemRepository();
4705
        $itemRoot = $lpItemRepo->getRootItem($this->get_id());
4706
4707
        $options = [
4708
            'decorate' => true,
4709
            'rootOpen' => function($tree) use ($noWrapper) {
4710
                if ($tree[0]['lvl'] === 1) {
4711
                    if ($noWrapper) {
4712
                        return '';
4713
                    }
4714
                    return '<ul id="lp_item_list" class="list-group nested-sortable">';
4715
                }
4716
4717
                return '<ul class="list-group nested-sortable">';
4718
            },
4719
            'rootClose' => function($tree) use ($noWrapper, $dropElement)  {
4720
                if ($tree[0]['lvl'] === 1) {
4721
                    if ($dropElement) {
4722
                        //return Display::return_message(get_lang('Drag and drop an element here'));
4723
                        //return $this->getDropElementHtml();
4724
                    }
4725
                    if ($noWrapper) {
4726
                        return '';
4727
                    }
4728
                }
4729
4730
                return '</ul>';
4731
            },
4732
            'childOpen' => function($child) {
4733
                $id = $child['iid'];
4734
                return '<li
4735
                    id="'.$id.'"
4736
                    data-id="'.$id.'"
4737
                    class=" flex flex-col list-group-item nested-'.$child['lvl'].'">';
4738
            },
4739
            'childClose' => '',
4740
            'nodeDecorator' => function ($node) use ($mainUrl, $previewImage, $upIcon, $downIcon) {
4741
                $fullTitle = $node['title'];
4742
                $title = cut($fullTitle, self::MAX_LP_ITEM_TITLE_LENGTH);
4743
                $itemId = $node['iid'];
4744
                $type = $node['itemType'];
4745
                $lpId = $this->get_id();
4746
4747
                $moveIcon = '';
4748
                if (TOOL_LP_FINAL_ITEM !== $type) {
4749
                    $moveIcon .= '<a class="moved" href="#">';
4750
                    $moveIcon .= Display::return_icon(
4751
                        'move_everywhere.png',
4752
                        get_lang('Move'),
4753
                        [],
4754
                        ICON_SIZE_TINY
4755
                    );
4756
                    $moveIcon .= '</a>';
4757
                }
4758
4759
                $iconName = str_replace(' ', '', $type);
4760
                $icon = Display::return_icon(
4761
                    'lp_'.$iconName.'.png',
4762
                    '',
4763
                    [],
4764
                    ICON_SIZE_TINY
4765
                );
4766
4767
                $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$itemId.'&lp_id='.$lpId;
4768
                $previewIcon = Display::url(
4769
                    $previewImage,
4770
                    $urlPreviewLink,
4771
                    [
4772
                        'target' => '_blank',
4773
                        'class' => 'btn btn-default',
4774
                        'data-title' => $title,
4775
                        'title' => $title,
4776
                    ]
4777
                );
4778
                $url = $mainUrl.'&view=build&id='.$itemId.'&lp_id='.$lpId;
4779
4780
                $preRequisitesIcon = Display::url(
4781
                    Display::return_icon(
4782
                        'accept.png',
4783
                        get_lang('Prerequisites'),
4784
                        [],
4785
                        ICON_SIZE_TINY
4786
                    ),
4787
                    $url.'&action=edit_item_prereq',
4788
                    ['class' => 'btn btn-default']
4789
                );
4790
4791
                //$editIcon = '';
4792
                $editIcon = '<a
4793
                    href="'.$mainUrl.'&action=edit_item&view=build&id='.$itemId.'&lp_id='.$lpId.'&path_item='.$node['path'].'"
4794
                    class="btn btn-default"
4795
                    >';
4796
                $editIcon .= Display::return_icon(
4797
                    'edit.png',
4798
                    get_lang('Edit section description/name'),
4799
                    [],
4800
                    ICON_SIZE_TINY
4801
                );
4802
                $editIcon .= '</a>';
4803
                $orderIcons = '';
4804
                /*if ('final_item' !== $type) {
4805
                    $orderIcons = Display::url(
4806
                        $upIcon,
4807
                        'javascript:void(0)',
4808
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $itemId]
4809
                    );
4810
                    $orderIcons .= Display::url(
4811
                        $downIcon,
4812
                        'javascript:void(0)',
4813
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $itemId]
4814
                    );
4815
                }*/
4816
4817
                $deleteIcon = ' <a
4818
                    data-id = '.$itemId.'
4819
                    data-title = \''.addslashes($title).'\'
4820
                    href="javascript:void(0);"
4821
                    onclick="return deleteItem(this);"
4822
                    class="btn btn-default">';
4823
                $deleteIcon .= Display::return_icon(
4824
                    'delete.png',
4825
                    get_lang('Delete section'),
4826
                    [],
4827
                    ICON_SIZE_TINY
4828
                );
4829
                $deleteIcon .= '</a>';
4830
                $extra = '';
4831
4832
                if ('dir' === $type && empty($node['__children'])) {
4833
                    $level = $node['lvl'] + 1;
4834
                    $extra = '<ul class="list-group nested-sortable">
4835
                                <li class="list-group-item list-group-item-empty nested-'.$level.'"></li>
4836
                              </ul>';
4837
                }
4838
                //return $title;
4839
                $buttons = Display::tag(
4840
                    'div',
4841
                    "<div class=\"btn-group btn-group-sm\">
4842
                                $editIcon
4843
                                $preRequisitesIcon
4844
                                $orderIcons
4845
                                $deleteIcon
4846
                               </div>",
4847
                    ['class' => 'btn-toolbar button_actions']
4848
                );
4849
                //$buttons = '';
4850
                //$extra = '';
4851
                //Display::span($title, ['title' => $fullTitle])
4852
                //return $title.                $extra;
4853
                return
4854
                    "<div class='flex flex-row'> $moveIcon  $icon <div>$title </div></div>
4855
                    $extra
4856
                    $buttons
4857
                    "
4858
                    ;
4859
            },
4860
        ];
4861
4862
        $tree = $lpItemRepo->childrenHierarchy($itemRoot, false, $options);
4863
4864
        if (empty($tree) && $dropElement) {
4865
            return $this->getDropElementHtml($noWrapper);
4866
        }
4867
4868
        return $tree;
4869
    }
4870
4871
    public function getDropElementHtml($noWrapper = false)
4872
    {
4873
        $li = '<li class="list-group-item">'.
4874
            Display::return_message(get_lang('Drag and drop an element here')).
4875
            '</li>';
4876
        if ($noWrapper) {
4877
            return $li;
4878
        }
4879
4880
        return
4881
            '<ul id="lp_item_list" class="list-group nested-sortable">
4882
            '.$li.'
4883
            </ul>';
4884
    }
4885
4886
    /**
4887
     * This function builds the action menu.
4888
     *
4889
     * @param bool   $returnString           Optional
4890
     * @param bool   $showRequirementButtons Optional. Allow show the requirements button
4891
     * @param bool   $isConfigPage           Optional. If is the config page, show the edit button
4892
     * @param bool   $allowExpand            Optional. Allow show the expand/contract button
4893
     * @param string $action
4894
     * @param array  $extraField
4895
     *
4896
     * @return string
4897
     */
4898
    public function build_action_menu(
4899
        $returnString = false,
4900
        $showRequirementButtons = true,
4901
        $isConfigPage = false,
4902
        $allowExpand = true,
4903
        $action = '',
4904
        $extraField = []
4905
    ) {
4906
        $actionsRight = '';
4907
        $lpId = $this->lp_id;
4908
        if (!isset($extraField['backTo']) && empty($extraField['backTo'])) {
4909
            $back = Display::url(
4910
                Display::return_icon(
4911
                    'back.png',
4912
                    get_lang('Back to learning paths'),
4913
                    '',
4914
                    ICON_SIZE_MEDIUM
4915
                ),
4916
                'lp_controller.php?'.api_get_cidreq()
4917
            );
4918
        } else {
4919
            $back = Display::url(
4920
                Display::return_icon(
4921
                    'back.png',
4922
                    get_lang('Back'),
4923
                    '',
4924
                    ICON_SIZE_MEDIUM
4925
                ),
4926
                $extraField['backTo']
4927
            );
4928
        }
4929
4930
        /*if ($backToBuild) {
4931
            $back = Display::url(
4932
                Display::return_icon(
4933
                    'back.png',
4934
                    get_lang('GoBack'),
4935
                    '',
4936
                    ICON_SIZE_MEDIUM
4937
                ),
4938
                "lp_controller.php?action=add_item&type=step&lp_id=$lpId&".api_get_cidreq()
4939
            );
4940
        }*/
4941
4942
        $actionsLeft = $back;
4943
4944
        $actionsLeft .= Display::url(
4945
            Display::return_icon(
4946
                'preview_view.png',
4947
                get_lang('Preview'),
4948
                '',
4949
                ICON_SIZE_MEDIUM
4950
            ),
4951
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
4952
                'action' => 'view',
4953
                'lp_id' => $lpId,
4954
                'isStudentView' => 'true',
4955
            ])
4956
        );
4957
4958
        /*$actionsLeft .= Display::url(
4959
            Display::return_icon(
4960
                'upload_audio.png',
4961
                get_lang('Add audio'),
4962
                '',
4963
                ICON_SIZE_MEDIUM
4964
            ),
4965
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
4966
                'action' => 'admin_view',
4967
                'lp_id' => $lpId,
4968
                'updateaudio' => 'true',
4969
            ])
4970
        );*/
4971
4972
        $subscriptionSettings = self::getSubscriptionSettings();
4973
4974
        $request = api_request_uri();
4975
        if (false === strpos($request, 'edit')) {
4976
            $actionsLeft .= Display::url(
4977
                Display::return_icon(
4978
                    'settings.png',
4979
                    get_lang('Course settings'),
4980
                    '',
4981
                    ICON_SIZE_MEDIUM
4982
                ),
4983
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
4984
                    'action' => 'edit',
4985
                    'lp_id' => $lpId,
4986
                ])
4987
            );
4988
        }
4989
4990
        if ((false === strpos($request, 'build') &&
4991
            false === strpos($request, 'add_item')) ||
4992
            in_array($action, ['add_audio'], true)
4993
        ) {
4994
            $actionsLeft .= Display::url(
4995
                Display::return_icon(
4996
                    'edit.png',
4997
                    get_lang('Edit'),
4998
                    '',
4999
                    ICON_SIZE_MEDIUM
5000
                ),
5001
                'lp_controller.php?'.http_build_query([
5002
                    'action' => 'build',
5003
                    'lp_id' => $lpId,
5004
                ]).'&'.api_get_cidreq()
5005
            );
5006
        }
5007
5008
        if (false === strpos(api_get_self(), 'lp_subscribe_users.php')) {
5009
            if (1 == $this->subscribeUsers &&
5010
                $subscriptionSettings['allow_add_users_to_lp']) {
5011
                $actionsLeft .= Display::url(
5012
                    Display::return_icon(
5013
                        'user.png',
5014
                        get_lang('Subscribe users to learning path'),
5015
                        '',
5016
                        ICON_SIZE_MEDIUM
5017
                    ),
5018
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=$lpId&".api_get_cidreq()
5019
                );
5020
            }
5021
        }
5022
5023
        if ($allowExpand) {
5024
            /*$actionsLeft .= Display::url(
5025
                Display::return_icon(
5026
                    'expand.png',
5027
                    get_lang('Expand'),
5028
                    ['id' => 'expand'],
5029
                    ICON_SIZE_MEDIUM
5030
                ).
5031
                Display::return_icon(
5032
                    'contract.png',
5033
                    get_lang('Collapse'),
5034
                    ['id' => 'contract', 'class' => 'hide'],
5035
                    ICON_SIZE_MEDIUM
5036
                ),
5037
                '#',
5038
                ['role' => 'button', 'id' => 'hide_bar_template']
5039
            );*/
5040
        }
5041
5042
        if ($showRequirementButtons) {
5043
            $buttons = [
5044
                [
5045
                    'title' => get_lang('Set previous step as prerequisite for each step'),
5046
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
5047
                        'action' => 'set_previous_step_as_prerequisite',
5048
                        'lp_id' => $lpId,
5049
                    ]),
5050
                ],
5051
                [
5052
                    'title' => get_lang('Clear all prerequisites'),
5053
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
5054
                        'action' => 'clear_prerequisites',
5055
                        'lp_id' => $lpId,
5056
                    ]),
5057
                ],
5058
            ];
5059
            $actionsRight = Display::groupButtonWithDropDown(
5060
                get_lang('Prerequisites options'),
5061
                $buttons,
5062
                true
5063
            );
5064
        }
5065
5066
        if (api_is_platform_admin() && isset($extraField['authorlp'])) {
5067
            $actionsLeft .= Display::url(
5068
                Display::return_icon(
5069
                    'add-groups.png',
5070
                    get_lang('Author'),
5071
                    '',
5072
                    ICON_SIZE_MEDIUM
5073
                ),
5074
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
5075
                    'action' => 'author_view',
5076
                    'lp_id' => $lpId,
5077
                ])
5078
            );
5079
        }
5080
5081
        $toolbar = Display::toolbarAction('actions-lp-controller', [$actionsLeft, $actionsRight]);
5082
5083
        if ($returnString) {
5084
            return $toolbar;
5085
        }
5086
5087
        echo $toolbar;
5088
    }
5089
5090
    /**
5091
     * Creates the default learning path folder.
5092
     *
5093
     * @param array $course
5094
     * @param int   $creatorId
5095
     *
5096
     * @return CDocument
5097
     */
5098
    public static function generate_learning_path_folder($course, $creatorId = 0)
5099
    {
5100
        // Creating learning_path folder
5101
        $dir = 'learning_path';
5102
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
5103
5104
        return create_unexisting_directory(
5105
            $course,
5106
            $creatorId,
5107
            0,
5108
            null,
5109
            0,
5110
            '',
5111
            $dir,
5112
            get_lang('Learning paths'),
5113
            0
5114
        );
5115
    }
5116
5117
    /**
5118
     * @param array  $course
5119
     * @param string $lp_name
5120
     * @param int    $creatorId
5121
     *
5122
     * @return CDocument
5123
     */
5124
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
5125
    {
5126
        $filepath = '';
5127
        $dir = '/learning_path/';
5128
5129
        if (empty($lp_name)) {
5130
            $lp_name = $this->name;
5131
        }
5132
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
5133
        $parent = self::generate_learning_path_folder($course, $creatorId);
5134
5135
        // Limits title size
5136
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
5137
        $dir = $dir.$title;
5138
5139
        // Creating LP folder
5140
        $folder = null;
5141
        if ($parent) {
5142
            $folder = create_unexisting_directory(
5143
                $course,
5144
                $creatorId,
5145
                0,
5146
                0,
5147
                0,
5148
                $filepath,
5149
                $dir,
5150
                $lp_name,
5151
                '',
5152
                false,
5153
                false,
5154
                $parent
5155
            );
5156
        }
5157
5158
        return $folder;
5159
    }
5160
5161
    /**
5162
     * Create a new document //still needs some finetuning.
5163
     *
5164
     * @param array  $courseInfo
5165
     * @param string $content
5166
     * @param string $title
5167
     * @param string $extension
5168
     * @param int    $parentId
5169
     * @param int    $creatorId  creator id
5170
     *
5171
     * @return int
5172
     */
5173
    public function create_document(
5174
        $courseInfo,
5175
        $content = '',
5176
        $title = '',
5177
        $extension = 'html',
5178
        $parentId = 0,
5179
        $creatorId = 0
5180
    ) {
5181
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
5182
        $sessionId = api_get_session_id();
5183
5184
        // Generates folder
5185
        $this->generate_lp_folder($courseInfo);
5186
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
5187
        // is already escaped twice when it gets here.
5188
        $originalTitle = !empty($title) ? $title : $_POST['title'];
5189
        if (!empty($title)) {
5190
            $title = api_replace_dangerous_char(stripslashes($title));
5191
        } else {
5192
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
5193
        }
5194
5195
        $title = disable_dangerous_file($title);
5196
        $filename = $title;
5197
        $tmp_filename = $filename;
5198
        /*$i = 0;
5199
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
5200
            $tmp_filename = $filename.'_'.++$i;
5201
        }*/
5202
        $filename = $tmp_filename.'.'.$extension;
5203
5204
        if ('html' === $extension) {
5205
            $content = stripslashes($content);
5206
            $content = str_replace(
5207
                api_get_path(WEB_COURSE_PATH),
5208
                api_get_path(REL_PATH).'courses/',
5209
                $content
5210
            );
5211
5212
            // Change the path of mp3 to absolute.
5213
            // The first regexp deals with :// urls.
5214
            /*$content = preg_replace(
5215
                "|(flashvars=\"file=)([^:/]+)/|",
5216
                "$1".api_get_path(
5217
                    REL_COURSE_PATH
5218
                ).$courseInfo['path'].'/document/',
5219
                $content
5220
            );*/
5221
            // The second regexp deals with audio/ urls.
5222
            /*$content = preg_replace(
5223
                "|(flashvars=\"file=)([^/]+)/|",
5224
                "$1".api_get_path(
5225
                    REL_COURSE_PATH
5226
                ).$courseInfo['path'].'/document/$2/',
5227
                $content
5228
            );*/
5229
            // For flv player: To prevent edition problem with firefox,
5230
            // we have to use a strange tip (don't blame me please).
5231
            $content = str_replace(
5232
                '</body>',
5233
                '<style type="text/css">body{}</style></body>',
5234
                $content
5235
            );
5236
        }
5237
5238
        $document = DocumentManager::addDocument(
5239
            $courseInfo,
5240
            null,
5241
            'file',
5242
            '',
5243
            $tmp_filename,
5244
            '',
5245
            0, //readonly
5246
            true,
5247
            null,
5248
            $sessionId,
5249
            $creatorId,
5250
            false,
5251
            $content,
5252
            $parentId
5253
        );
5254
5255
        $document_id = $document->getIid();
5256
        if ($document_id) {
5257
            $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
5258
            $new_title = $originalTitle;
5259
5260
            if ($new_comment || $new_title) {
5261
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
5262
                $ct = '';
5263
                if ($new_comment) {
5264
                    $ct .= ", comment='".Database::escape_string($new_comment)."'";
5265
                }
5266
                if ($new_title) {
5267
                    $ct .= ", title='".Database::escape_string($new_title)."' ";
5268
                }
5269
5270
                $sql = "UPDATE $tbl_doc SET ".substr($ct, 1)."
5271
                        WHERE iid = $document_id ";
5272
                Database::query($sql);
5273
            }
5274
        }
5275
5276
        return $document_id;
5277
    }
5278
5279
    /**
5280
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
5281
     */
5282
    public function edit_document()
5283
    {
5284
        $repo = Container::getDocumentRepository();
5285
        if (isset($_REQUEST['document_id']) && !empty($_REQUEST['document_id'])) {
5286
            $id = (int) $_REQUEST['document_id'];
5287
            /** @var CDocument $document */
5288
            $document = $repo->find($id);
5289
            if ($document->getResourceNode()->hasEditableTextContent()) {
5290
                $repo->updateResourceFileContent($document, $_REQUEST['content_lp']);
5291
            }
5292
            $document->setTitle($_REQUEST['title']);
5293
            $repo->update($document);
5294
        }
5295
    }
5296
5297
    /**
5298
     * Displays the selected item, with a panel for manipulating the item.
5299
     *
5300
     * @param CLpItem $lpItem
5301
     * @param string  $msg
5302
     * @param bool    $show_actions
5303
     *
5304
     * @return string
5305
     */
5306
    public function display_item($lpItem, $msg = null, $show_actions = true)
5307
    {
5308
        $course_id = api_get_course_int_id();
5309
        $return = '';
5310
5311
        if (null === $lpItem) {
5312
            return '';
5313
        }
5314
        $item_id = $lpItem->getIid();
5315
        $itemType = $lpItem->getItemType();
5316
        $lpId = $lpItem->getLp()->getIid();
5317
        $path = $lpItem->getPath();
5318
5319
        Session::write('parent_item_id', 'dir' === $itemType ? $item_id : 0);
5320
5321
        // Prevents wrong parent selection for document, see Bug#1251.
5322
        if ('dir' !== $itemType) {
5323
            Session::write('parent_item_id', $lpItem->getParentItemId());
5324
        }
5325
5326
        if ($show_actions) {
5327
            $return .= $this->displayItemMenu($lpItem);
5328
        }
5329
        $return .= '<div style="padding:10px;">';
5330
5331
        if ('' != $msg) {
5332
            $return .= $msg;
5333
        }
5334
5335
        $return .= '<h3>'.$lpItem->getTitle().'</h3>';
5336
5337
        switch ($itemType) {
5338
            case TOOL_THREAD:
5339
                $link = $this->rl_get_resource_link_for_learnpath(
5340
                    $course_id,
5341
                    $lpId,
5342
                    $item_id,
5343
                    0
5344
                );
5345
                $return .= Display::url(
5346
                    get_lang('Go to thread'),
5347
                    $link,
5348
                    ['class' => 'btn btn-primary']
5349
                );
5350
                break;
5351
            case TOOL_FORUM:
5352
                $return .= Display::url(
5353
                    get_lang('Go to the forum'),
5354
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$path,
5355
                    ['class' => 'btn btn-primary']
5356
                );
5357
                break;
5358
            case TOOL_QUIZ:
5359
                if (!empty($path)) {
5360
                    $exercise = new Exercise();
5361
                    $exercise->read($path);
5362
                    $return .= $exercise->description.'<br />';
5363
                    $return .= Display::url(
5364
                        get_lang('Go to exercise'),
5365
                        api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
5366
                        ['class' => 'btn btn-primary']
5367
                    );
5368
                }
5369
                break;
5370
            case TOOL_LP_FINAL_ITEM:
5371
                $return .= $this->getSavedFinalItem();
5372
                break;
5373
            case TOOL_DOCUMENT:
5374
            case TOOL_READOUT_TEXT:
5375
                $repo = Container::getDocumentRepository();
5376
                /** @var CDocument $document */
5377
                $document = $repo->find($lpItem->getPath());
5378
                $return .= $this->display_document($document, true, true);
5379
                break;
5380
        }
5381
        $return .= '</div>';
5382
5383
        return $return;
5384
    }
5385
5386
    /**
5387
     * Shows the needed forms for editing a specific item.
5388
     *
5389
     * @param CLpItem $lpItem
5390
     *
5391
     * @throws Exception
5392
     *
5393
     *
5394
     * @return string
5395
     */
5396
    public function display_edit_item($lpItem, $excludeExtraFields = [])
5397
    {
5398
        $return = '';
5399
        if (empty($lpItem)) {
5400
            return '';
5401
        }
5402
        $itemType = $lpItem->getItemType();
5403
        $path = $lpItem->getPath();
5404
5405
        switch ($itemType) {
5406
            case 'dir':
5407
            case 'asset':
5408
            case 'sco':
5409
                if (isset($_GET['view']) && 'build' === $_GET['view']) {
5410
                    $return .= $this->displayItemMenu($lpItem);
5411
                    $return .= $this->display_item_form($lpItem, 'edit');
5412
                } else {
5413
                    $return .= $this->display_item_form($lpItem, 'edit_item');
5414
                }
5415
                break;
5416
            case TOOL_LP_FINAL_ITEM:
5417
            case TOOL_DOCUMENT:
5418
            case TOOL_READOUT_TEXT:
5419
                $return .= $this->displayItemMenu($lpItem);
5420
                $return .= $this->displayDocumentForm('edit', $lpItem);
5421
                break;
5422
            case TOOL_LINK:
5423
                $link = null;
5424
                if (!empty($path)) {
5425
                    $repo = Container::getLinkRepository();
5426
                    $link = $repo->find($path);
5427
                }
5428
                $return .= $this->displayItemMenu($lpItem);
5429
                $return .= $this->display_link_form('edit', $lpItem, $link);
5430
5431
                break;
5432
            case TOOL_QUIZ:
5433
                if (!empty($path)) {
5434
                    $repo = Container::getQuizRepository();
5435
                    $resource = $repo->find($path);
5436
                }
5437
                $return .= $this->displayItemMenu($lpItem);
5438
                $return .= $this->display_quiz_form('edit', $lpItem, $resource);
5439
                break;
5440
            case TOOL_STUDENTPUBLICATION:
5441
                if (!empty($path)) {
5442
                    $repo = Container::getStudentPublicationRepository();
5443
                    $resource = $repo->find($path);
5444
                }
5445
                $return .= $this->displayItemMenu($lpItem);
5446
                $return .= $this->display_student_publication_form('edit', $lpItem, $resource);
5447
                break;
5448
            case TOOL_FORUM:
5449
                if (!empty($path)) {
5450
                    $repo = Container::getForumRepository();
5451
                    $resource = $repo->find($path);
5452
                }
5453
                $return .= $this->displayItemMenu($lpItem);
5454
                $return .= $this->display_forum_form('edit', $lpItem, $resource);
5455
                break;
5456
            case TOOL_THREAD:
5457
                if (!empty($path)) {
5458
                    $repo = Container::getForumPostRepository();
5459
                    $resource = $repo->find($path);
5460
                }
5461
                $return .= $this->displayItemMenu($lpItem);
5462
                $return .= $this->display_thread_form('edit', $lpItem, $resource);
5463
                break;
5464
        }
5465
5466
        return $return;
5467
    }
5468
5469
    /**
5470
     * Function that displays a list with al the resources that
5471
     * could be added to the learning path.
5472
     *
5473
     * @throws Exception
5474
     */
5475
    public function displayResources(): string
5476
    {
5477
        // Get all the docs.
5478
        $documents = $this->get_documents(true);
5479
5480
        // Get all the exercises.
5481
        $exercises = $this->get_exercises();
5482
5483
        // Get all the links.
5484
        $links = $this->get_links();
5485
5486
        // Get all the student publications.
5487
        $works = $this->get_student_publications();
5488
5489
        // Get all the forums.
5490
        $forums = $this->get_forums();
5491
5492
        // Get the final item form (see BT#11048) .
5493
        $finish = $this->getFinalItemForm();
5494
        $size = ICON_SIZE_MEDIUM; //ICON_SIZE_BIG
5495
        $headers = [
5496
            Display::return_icon('folder_document.png', get_lang('Documents'), [], $size),
5497
            Display::return_icon('quiz.png', get_lang('Tests'), [], $size),
5498
            Display::return_icon('links.png', get_lang('Links'), [], $size),
5499
            Display::return_icon('works.png', get_lang('Assignments'), [], $size),
5500
            Display::return_icon('forum.png', get_lang('Forums'), [], $size),
5501
            Display::return_icon('add_learnpath_section.png', get_lang('Add section'), [], $size),
5502
            Display::return_icon('certificate.png', get_lang('Certificate'), [], $size),
5503
        ];
5504
        $content = '';
5505
        /*$content = Display::return_message(
5506
            get_lang('Click on the [Learner view] button to see your learning path'),
5507
            'normal'
5508
        );*/
5509
        $section = $this->displayNewSectionForm();
5510
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
5511
5512
        return Display::tabs(
5513
            $headers,
5514
            [
5515
                $documents,
5516
                $exercises,
5517
                $links,
5518
                $works,
5519
                $forums,
5520
                $section,
5521
                $finish,
5522
            ],
5523
            'resource_tab',
5524
            [],
5525
            [],
5526
            $selected
5527
        );
5528
    }
5529
5530
    /**
5531
     * Returns the extension of a document.
5532
     *
5533
     * @param string $filename
5534
     *
5535
     * @return string Extension (part after the last dot)
5536
     */
5537
    public function get_extension($filename)
5538
    {
5539
        $explode = explode('.', $filename);
5540
5541
        return $explode[count($explode) - 1];
5542
    }
5543
5544
    /**
5545
     * @return string
5546
     */
5547
    public function getCurrentBuildingModeURL()
5548
    {
5549
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
5550
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
5551
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
5552
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
5553
5554
        $currentUrl = api_get_self().'?'.api_get_cidreq().
5555
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
5556
5557
        return $currentUrl;
5558
    }
5559
5560
    /**
5561
     * Displays a document by id.
5562
     *
5563
     * @param CDocument $document
5564
     * @param bool      $show_title
5565
     * @param bool      $iframe
5566
     * @param bool      $edit_link
5567
     *
5568
     * @return string
5569
     */
5570
    public function display_document($document, $show_title = false, $iframe = true, $edit_link = false)
5571
    {
5572
        $return = '';
5573
        if (!$document) {
5574
            return '';
5575
        }
5576
5577
        $repo = Container::getDocumentRepository();
5578
5579
        // TODO: Add a path filter.
5580
        if ($iframe) {
5581
            $url = $repo->getResourceFileUrl($document);
5582
5583
            $return .= '<iframe
5584
                id="learnpath_preview_frame"
5585
                frameborder="0"
5586
                height="400"
5587
                width="100%"
5588
                scrolling="auto"
5589
                src="'.$url.'"></iframe>';
5590
        } else {
5591
            $return = $repo->getResourceFileContent($document);
5592
        }
5593
5594
        return $return;
5595
    }
5596
5597
    /**
5598
     * Return HTML form to add/edit a link item.
5599
     *
5600
     * @param string  $action (add/edit)
5601
     * @param CLpItem $lpItem
5602
     * @param CLink   $link
5603
     *
5604
     * @throws Exception
5605
     *
5606
     *
5607
     * @return string HTML form
5608
     */
5609
    public function display_link_form($action, $lpItem, $link)
5610
    {
5611
        $item_url = '';
5612
        if ($link) {
5613
            $item_url = stripslashes($link->getUrl());
5614
        }
5615
        $form = new FormValidator(
5616
            'edit_link',
5617
            'POST',
5618
            $this->getCurrentBuildingModeURL()
5619
        );
5620
5621
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
5622
5623
        $urlAttributes = ['class' => 'learnpath_item_form'];
5624
        $urlAttributes['disabled'] = 'disabled';
5625
        $form->addElement('url', 'url', get_lang('URL'), $urlAttributes);
5626
        $form->setDefault('url', $item_url);
5627
5628
        $form->addButtonSave(get_lang('Save'), 'submit_button');
5629
5630
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
5631
    }
5632
5633
    /**
5634
     * Return HTML form to add/edit a quiz.
5635
     *
5636
     * @param string  $action   Action (add/edit)
5637
     * @param CLpItem $lpItem   Item ID if already exists
5638
     * @param CQuiz   $exercise Extra information (quiz ID if integer)
5639
     *
5640
     * @throws Exception
5641
     *
5642
     * @return string HTML form
5643
     */
5644
    public function display_quiz_form($action, $lpItem, $exercise)
5645
    {
5646
        $form = new FormValidator(
5647
            'quiz_form',
5648
            'POST',
5649
            $this->getCurrentBuildingModeURL()
5650
        );
5651
5652
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
5653
        $form->addButtonSave(get_lang('Save'), 'submit_button');
5654
5655
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
5656
    }
5657
5658
    /**
5659
     * Return the form to display the forum edit/add option.
5660
     *
5661
     * @param CLpItem $lpItem
5662
     *
5663
     * @throws Exception
5664
     *
5665
     * @return string HTML form
5666
     */
5667
    public function display_forum_form($action, $lpItem, $resource)
5668
    {
5669
        $form = new FormValidator(
5670
            'forum_form',
5671
            'POST',
5672
            $this->getCurrentBuildingModeURL()
5673
        );
5674
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
5675
5676
        if ('add' === $action) {
5677
            $form->addButtonSave(get_lang('Add forum to course'), 'submit_button');
5678
        } else {
5679
            $form->addButtonSave(get_lang('Edit the current forum'), 'submit_button');
5680
        }
5681
5682
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
5683
    }
5684
5685
    /**
5686
     * Return HTML form to add/edit forum threads.
5687
     *
5688
     * @param string  $action
5689
     * @param CLpItem $lpItem
5690
     * @param string  $resource
5691
     *
5692
     * @throws Exception
5693
     *
5694
     * @return string HTML form
5695
     */
5696
    public function display_thread_form($action, $lpItem, $resource)
5697
    {
5698
        $form = new FormValidator(
5699
            'thread_form',
5700
            'POST',
5701
            $this->getCurrentBuildingModeURL()
5702
        );
5703
5704
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
5705
5706
        $form->addButtonSave(get_lang('Save'), 'submit_button');
5707
5708
        return $form->returnForm();
5709
    }
5710
5711
    /**
5712
     * Return the HTML form to display an item (generally a dir item).
5713
     *
5714
     * @param CLpItem $lpItem
5715
     * @param string  $action
5716
     *
5717
     * @throws Exception
5718
     *
5719
     *
5720
     * @return string HTML form
5721
     */
5722
    public function display_item_form(
5723
        $lpItem,
5724
        $action = 'add_item'
5725
    ) {
5726
        $item_type = $lpItem->getItemType();
5727
5728
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
5729
5730
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
5731
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
5732
5733
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
5734
5735
        return $form->returnForm();
5736
    }
5737
5738
    /**
5739
     * Return HTML form to add/edit a student publication (work).
5740
     *
5741
     * @param string              $action
5742
     * @param CStudentPublication $resource
5743
     *
5744
     * @throws Exception
5745
     *
5746
     * @return string HTML form
5747
     */
5748
    public function display_student_publication_form($action, CLpItem $lpItem, $resource)
5749
    {
5750
        $form = new FormValidator('frm_student_publication', 'post', '#');
5751
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
5752
5753
        $form->addButtonSave(get_lang('Save'), 'submit_button');
5754
5755
        $return = '<div class="sectioncomment">';
5756
        $return .= $form->returnForm();
5757
        $return .= '</div>';
5758
5759
        return $return;
5760
    }
5761
5762
    public function displayNewSectionForm()
5763
    {
5764
        $action = 'add_item';
5765
        $item_type = 'dir';
5766
5767
        $lpItem = (new CLpItem())
5768
            ->setTitle('')
5769
            ->setItemType('dir')
5770
        ;
5771
5772
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
5773
5774
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
5775
        LearnPathItemForm::setForm($form, 'add', $this, $lpItem);
5776
5777
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
5778
        $form->addElement('hidden', 'type', 'dir');
5779
5780
        return $form->returnForm();
5781
    }
5782
5783
    /**
5784
     * Returns the form to update or create a document.
5785
     *
5786
     * @param string  $action (add/edit)
5787
     * @param CLpItem $lpItem
5788
     *
5789
     *
5790
     * @throws Exception
5791
     *
5792
     * @return string HTML form
5793
     */
5794
    public function displayDocumentForm($action = 'add', $lpItem = null)
5795
    {
5796
        $courseInfo = api_get_course_info();
5797
5798
        $form = new FormValidator(
5799
            'form',
5800
            'POST',
5801
            $this->getCurrentBuildingModeURL(),
5802
            '',
5803
            ['enctype' => 'multipart/form-data']
5804
        );
5805
5806
        $data = $this->generate_lp_folder($courseInfo);
5807
5808
        if (null !== $lpItem) {
5809
            LearnPathItemForm::setForm($form, $action, $this, $lpItem);
5810
        }
5811
5812
        switch ($action) {
5813
            case 'add':
5814
                $folders = DocumentManager::get_all_document_folders(
5815
                    $courseInfo,
5816
                    0,
5817
                    true
5818
                );
5819
                DocumentManager::build_directory_selector(
5820
                    $folders,
5821
                    '',
5822
                    [],
5823
                    true,
5824
                    $form,
5825
                    'directory_parent_id'
5826
                );
5827
5828
                if ($data) {
5829
                    $defaults['directory_parent_id'] = $data->getIid();
5830
                }
5831
5832
                break;
5833
        }
5834
5835
        $form->addButtonSave(get_lang('Save'), 'submit_button');
5836
5837
        return $form->returnForm();
5838
    }
5839
5840
    /**
5841
     * @param array  $courseInfo
5842
     * @param string $content
5843
     * @param string $title
5844
     * @param int    $parentId
5845
     *
5846
     * @return int
5847
     */
5848
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
5849
    {
5850
        $creatorId = api_get_user_id();
5851
        $sessionId = api_get_session_id();
5852
5853
        // Generates folder
5854
        $result = $this->generate_lp_folder($courseInfo);
5855
        $dir = $result['dir'];
5856
5857
        if (empty($parentId) || '/' === $parentId) {
5858
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
5859
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
5860
5861
            if ('/' === $parentId) {
5862
                $dir = '/';
5863
            }
5864
5865
            // Please, do not modify this dirname formatting.
5866
            if (strstr($dir, '..')) {
5867
                $dir = '/';
5868
            }
5869
5870
            if (!empty($dir[0]) && '.' == $dir[0]) {
5871
                $dir = substr($dir, 1);
5872
            }
5873
            if (!empty($dir[0]) && '/' != $dir[0]) {
5874
                $dir = '/'.$dir;
5875
            }
5876
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
5877
                $dir .= '/';
5878
            }
5879
        } else {
5880
            $parentInfo = DocumentManager::get_document_data_by_id(
5881
                $parentId,
5882
                $courseInfo['code']
5883
            );
5884
            if (!empty($parentInfo)) {
5885
                $dir = $parentInfo['path'].'/';
5886
            }
5887
        }
5888
5889
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
5890
5891
        if (!is_dir($filepath)) {
5892
            $dir = '/';
5893
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
5894
        }
5895
5896
        $originalTitle = !empty($title) ? $title : $_POST['title'];
5897
5898
        if (!empty($title)) {
5899
            $title = api_replace_dangerous_char(stripslashes($title));
5900
        } else {
5901
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
5902
        }
5903
5904
        $title = disable_dangerous_file($title);
5905
        $filename = $title;
5906
        $content = !empty($content) ? $content : $_POST['content_lp'];
5907
        $tmpFileName = $filename;
5908
5909
        $i = 0;
5910
        while (file_exists($filepath.$tmpFileName.'.html')) {
5911
            $tmpFileName = $filename.'_'.++$i;
5912
        }
5913
5914
        $filename = $tmpFileName.'.html';
5915
        $content = stripslashes($content);
5916
5917
        if (file_exists($filepath.$filename)) {
5918
            return 0;
5919
        }
5920
5921
        $putContent = file_put_contents($filepath.$filename, $content);
5922
5923
        if (false === $putContent) {
5924
            return 0;
5925
        }
5926
5927
        $fileSize = filesize($filepath.$filename);
5928
        $saveFilePath = $dir.$filename;
5929
5930
        $document = DocumentManager::addDocument(
5931
            $courseInfo,
5932
            $saveFilePath,
5933
            'file',
5934
            $fileSize,
5935
            $tmpFileName,
5936
            '',
5937
            0, //readonly
5938
            true,
5939
            null,
5940
            $sessionId,
5941
            $creatorId
5942
        );
5943
5944
        $documentId = $document->getIid();
5945
5946
        if (!$document) {
5947
            return 0;
5948
        }
5949
5950
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
5951
        $newTitle = $originalTitle;
5952
5953
        if ($newComment || $newTitle) {
5954
            $em = Database::getManager();
5955
5956
            if ($newComment) {
5957
                $document->setComment($newComment);
5958
            }
5959
5960
            if ($newTitle) {
5961
                $document->setTitle($newTitle);
5962
            }
5963
5964
            $em->persist($document);
5965
            $em->flush();
5966
        }
5967
5968
        return $documentId;
5969
    }
5970
5971
    /**
5972
     * Displays the menu for manipulating a step.
5973
     *
5974
     * @return string
5975
     */
5976
    public function displayItemMenu(CLpItem $lpItem)
5977
    {
5978
        $item_id = $lpItem->getIid();
5979
        $audio = $lpItem->getAudio();
5980
        $itemType = $lpItem->getItemType();
5981
        $path = $lpItem->getPath();
5982
5983
        $return = '';
5984
        $audio_player = null;
5985
        // We display an audio player if needed.
5986
        if (!empty($audio)) {
5987
            /*$webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
5988
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
5989
                .'<audio src="'.$webAudioPath.'" controls>'
5990
                .'</div><br>';*/
5991
        }
5992
5993
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
5994
5995
        if (TOOL_LP_FINAL_ITEM !== $itemType) {
5996
            $return .= Display::url(
5997
                Display::return_icon(
5998
                    'edit.png',
5999
                    get_lang('Edit'),
6000
                    [],
6001
                    ICON_SIZE_SMALL
6002
                ),
6003
                $url.'&action=edit_item&path_item='.$path
6004
            );
6005
6006
            /*$return .= Display::url(
6007
                Display::return_icon(
6008
                    'move.png',
6009
                    get_lang('Move'),
6010
                    [],
6011
                    ICON_SIZE_SMALL
6012
                ),
6013
                $url.'&action=move_item'
6014
            );*/
6015
        }
6016
6017
        // Commented for now as prerequisites cannot be added to chapters.
6018
        if ('dir' !== $itemType) {
6019
            $return .= Display::url(
6020
                Display::return_icon(
6021
                    'accept.png',
6022
                    get_lang('Prerequisites'),
6023
                    [],
6024
                    ICON_SIZE_SMALL
6025
                ),
6026
                $url.'&action=edit_item_prereq'
6027
            );
6028
        }
6029
        $return .= Display::url(
6030
            Display::return_icon(
6031
                'delete.png',
6032
                get_lang('Delete'),
6033
                [],
6034
                ICON_SIZE_SMALL
6035
            ),
6036
            $url.'&action=delete_item'
6037
        );
6038
6039
        /*if (in_array($itemType, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
6040
            $documentData = DocumentManager::get_document_data_by_id($path, $course_code);
6041
            if (empty($documentData)) {
6042
                // Try with iid
6043
                $table = Database::get_course_table(TABLE_DOCUMENT);
6044
                $sql = "SELECT path FROM $table
6045
                        WHERE
6046
                              c_id = ".api_get_course_int_id()." AND
6047
                              iid = ".$path." AND
6048
                              path NOT LIKE '%_DELETED_%'";
6049
                $result = Database::query($sql);
6050
                $documentData = Database::fetch_array($result);
6051
                if ($documentData) {
6052
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
6053
                }
6054
            }
6055
            if (isset($documentData['absolute_path_from_document'])) {
6056
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
6057
            }
6058
        }*/
6059
6060
        if (!empty($audio_player)) {
6061
            $return .= $audio_player;
6062
        }
6063
6064
        return Display::toolbarAction('lp_item', [$return]);
6065
    }
6066
6067
    /**
6068
     * Creates the javascript needed for filling up the checkboxes without page reload.
6069
     *
6070
     * @return string
6071
     */
6072
    public function get_js_dropdown_array()
6073
    {
6074
        $return = 'var child_name = new Array();'."\n";
6075
        $return .= 'var child_value = new Array();'."\n\n";
6076
        $return .= 'child_name[0] = new Array();'."\n";
6077
        $return .= 'child_value[0] = new Array();'."\n\n";
6078
6079
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
6080
        $sql = "SELECT * FROM ".$tbl_lp_item."
6081
                WHERE
6082
                    lp_id = ".$this->lp_id." AND
6083
                    parent_item_id = 0
6084
                ORDER BY display_order ASC";
6085
        Database::query($sql);
6086
        $i = 0;
6087
6088
        $list = $this->getItemsForForm(true);
6089
6090
        foreach ($list as $row_zero) {
6091
            if (TOOL_LP_FINAL_ITEM !== $row_zero['item_type']) {
6092
                if (TOOL_QUIZ == $row_zero['item_type']) {
6093
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
6094
                }
6095
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
6096
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
6097
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
6098
            }
6099
        }
6100
6101
        $return .= "\n";
6102
        $sql = "SELECT * FROM $tbl_lp_item
6103
                WHERE lp_id = ".$this->lp_id;
6104
        $res = Database::query($sql);
6105
        while ($row = Database::fetch_array($res)) {
6106
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
6107
                           WHERE
6108
                                parent_item_id = ".$row['iid']."
6109
                           ORDER BY display_order ASC";
6110
            $res_parent = Database::query($sql_parent);
6111
            $i = 0;
6112
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
6113
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
6114
6115
            while ($row_parent = Database::fetch_array($res_parent)) {
6116
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
6117
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
6118
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
6119
            }
6120
            $return .= "\n";
6121
        }
6122
6123
        $return .= "
6124
            function load_cbo(id) {
6125
                if (!id) {
6126
                    return false;
6127
                }
6128
6129
                var cbo = document.getElementById('previous');
6130
                if (cbo) {
6131
                    for(var i = cbo.length - 1; i > 0; i--) {
6132
                        cbo.options[i] = null;
6133
                    }                    
6134
                    var k=0;
6135
                    for (var i = 1; i <= child_name[id].length; i++){
6136
                        var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
6137
                        option.style.paddingLeft = '40px';
6138
                        cbo.options[i] = option;
6139
                        k = i;
6140
                    }    
6141
                    cbo.options[k].selected = true;
6142
                }
6143
6144
                //$('#previous').selectpicker('refresh');
6145
            }";
6146
6147
        return $return;
6148
    }
6149
6150
    /**
6151
     * Display the form to allow moving an item.
6152
     *
6153
     * @param CLpItem $lpItem
6154
     *
6155
     * @throws Exception
6156
     *
6157
     *
6158
     * @return string HTML form
6159
     */
6160
    public function display_move_item($lpItem)
6161
    {
6162
        $return = '';
6163
        $path = $lpItem->getPath();
6164
6165
        if ($lpItem) {
6166
            $itemType = $lpItem->getItemType();
6167
            switch ($itemType) {
6168
                case 'dir':
6169
                case 'asset':
6170
                    $return .= $this->displayItemMenu($lpItem);
6171
                    $return .= $this->display_item_form(
6172
                        $lpItem,
6173
                        get_lang('Move the current section'),
6174
                        'move',
6175
                        $row
6176
                    );
6177
                    break;
6178
                case TOOL_DOCUMENT:
6179
                    $return .= $this->displayItemMenu($lpItem);
6180
                    $return .= $this->displayDocumentForm('move', $lpItem);
6181
                    break;
6182
                case TOOL_LINK:
6183
                    $link = null;
6184
                    if (!empty($path)) {
6185
                        $repo = Container::getLinkRepository();
6186
                        $link = $repo->find($path);
6187
                    }
6188
                    $return .= $this->displayItemMenu($lpItem);
6189
                    $return .= $this->display_link_form('move', $lpItem, $link);
6190
                    break;
6191
                case TOOL_HOTPOTATOES:
6192
                    $return .= $this->displayItemMenu($lpItem);
6193
                    $return .= $this->display_link_form('move', $lpItem, $row);
6194
                    break;
6195
                case TOOL_QUIZ:
6196
                    $return .= $this->displayItemMenu($lpItem);
6197
                    $return .= $this->display_quiz_form('move', $lpItem, $row);
6198
                    break;
6199
                case TOOL_STUDENTPUBLICATION:
6200
                    $return .= $this->displayItemMenu($lpItem);
6201
                    $return .= $this->display_student_publication_form('move', $lpItem, $row);
6202
                    break;
6203
                case TOOL_FORUM:
6204
                    $return .= $this->displayItemMenu($lpItem);
6205
                    $return .= $this->display_forum_form('move', $lpItem, $row);
6206
                    break;
6207
                case TOOL_THREAD:
6208
                    $return .= $this->displayItemMenu($lpItem);
6209
                    $return .= $this->display_forum_form('move', $lpItem, $row);
6210
                    break;
6211
            }
6212
        }
6213
6214
        return $return;
6215
    }
6216
6217
    /**
6218
     * Return HTML form to allow prerequisites selection.
6219
     *
6220
     * @todo use FormValidator
6221
     *
6222
     * @return string HTML form
6223
     */
6224
    public function display_item_prerequisites_form(CLpItem $lpItem)
6225
    {
6226
        $course_id = api_get_course_int_id();
6227
        $preRequisiteId = $lpItem->getPrerequisite();
6228
        $itemId = $lpItem->getIid();
6229
6230
        $return = Display::page_header(get_lang('Add/edit prerequisites').' '.$lpItem->getTitle());
6231
6232
        $return .= '<form method="POST">';
6233
        $return .= '<div class="table-responsive">';
6234
        $return .= '<table class="table table-hover">';
6235
        $return .= '<thead>';
6236
        $return .= '<tr>';
6237
        $return .= '<th>'.get_lang('Prerequisites').'</th>';
6238
        $return .= '<th width="140">'.get_lang('minimum').'</th>';
6239
        $return .= '<th width="140">'.get_lang('maximum').'</th>';
6240
        $return .= '</tr>';
6241
        $return .= '</thead>';
6242
6243
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
6244
        $return .= '<tbody>';
6245
        $return .= '<tr>';
6246
        $return .= '<td colspan="3">';
6247
        $return .= '<div class="radio learnpath"><label for="idnone">';
6248
        $return .= '<input checked="checked" id="idnone" name="prerequisites" type="radio" />';
6249
        $return .= get_lang('none').'</label>';
6250
        $return .= '</div>';
6251
        $return .= '</tr>';
6252
6253
        // @todo use entitites
6254
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
6255
        $sql = "SELECT * FROM $tbl_lp_item
6256
                WHERE lp_id = ".$this->lp_id;
6257
        $result = Database::query($sql);
6258
6259
        $selectedMinScore = [];
6260
        $selectedMaxScore = [];
6261
        $masteryScore = [];
6262
        while ($row = Database::fetch_array($result)) {
6263
            if ($row['iid'] == $itemId) {
6264
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
6265
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
6266
            }
6267
            $masteryScore[$row['iid']] = $row['mastery_score'];
6268
        }
6269
6270
        $displayOrder = $lpItem->getDisplayOrder();
6271
        $lpItemRepo = Container::getLpItemRepository();
6272
        $itemRoot = $lpItemRepo->getRootItem($this->get_id());
6273
        $em = Database::getManager();
6274
6275
        $currentItemId = $itemId;
6276
        $options = [
6277
            'decorate' => true,
6278
            'rootOpen' => function() {
6279
                return '';
6280
            },
6281
            'rootClose' => function() {
6282
                return '';
6283
            },
6284
            'childOpen' => function() {
6285
                return '';
6286
            },
6287
            'childClose' => '',
6288
            'nodeDecorator' => function ($item) use (
6289
                $currentItemId,
6290
                $preRequisiteId,
6291
                $course_id,
6292
                $selectedMaxScore,
6293
                $selectedMinScore,
6294
                $displayOrder,
6295
                $lpItemRepo,
6296
                $em
6297
            ) {
6298
                $mainUrl = '';
6299
                $fullTitle = $item['title'];
6300
                $title = cut($fullTitle, self::MAX_LP_ITEM_TITLE_LENGTH);
6301
                $itemId = $item['iid'];
6302
                $type = $item['itemType'];
6303
                $lpId = $this->get_id();
6304
                $iconName = str_replace(' ', '', $type);
6305
                $icon = Display::return_icon(
6306
                    'lp_'.$iconName.'.png',
6307
                    '',
6308
                    [],
6309
                    ICON_SIZE_TINY
6310
                );
6311
                $url = $mainUrl.'&view=build&id='.$itemId.'&lp_id='.$lpId;
6312
6313
                if ($itemId == $currentItemId) {
6314
                    return '';
6315
                }
6316
6317
                if ($displayOrder < $item['displayOrder']) {
6318
                    return '';
6319
                }
6320
6321
                $selectedMaxScoreValue = isset($selectedMaxScore[$itemId]) ? $selectedMaxScore[$itemId] : $item['maxScore'];
6322
                $selectedMinScoreValue = $selectedMinScore[$itemId] ?? 0;
6323
                $masteryScoreAsMinValue = $masteryScore[$itemId] ?? 0;
6324
6325
                $return = '<tr>';
6326
                $return .= '<td '.((TOOL_QUIZ != $type && TOOL_HOTPOTATOES != $type) ? ' colspan="3"' : '').'>';
6327
                $return .= '<div style="margin-left:'.($item['lvl'] * 20).'px;" class="radio learnpath">';
6328
                $return .= '<label for="id'.$itemId.'">';
6329
6330
                $checked = '';
6331
                if (null !== $preRequisiteId) {
6332
                    $checked = in_array($preRequisiteId, [$itemId, $item['ref']]) ? ' checked="checked" ' : '';
6333
                }
6334
6335
                $disabled = 'dir' === $type ? ' disabled="disabled" ' : '';
6336
6337
                $return .= '<input
6338
                    '.$checked.' '.$disabled.'
6339
                    id="id'.$itemId.'"
6340
                    name="prerequisites"
6341
                    type="radio"
6342
                    value="'.$itemId.'" />';
6343
6344
                $return .= $icon.'&nbsp;&nbsp;'.$item['title'].'</label>';
6345
                $return .= '</div>';
6346
                $return .= '</td>';
6347
6348
                if (TOOL_QUIZ == $type) {
6349
                    // lets update max_score Tests information depending of the Tests Advanced properties
6350
                    $exercise = new Exercise($course_id);
6351
                    /** @var CLpItem $itemEntity */
6352
                    $itemEntity = $lpItemRepo->find($itemId);
6353
                    $exercise->read($item['path']);
6354
                    $itemEntity->setMaxScore($exercise->get_max_score());
6355
                    $em->persist($itemEntity);
6356
                    $em->flush($itemEntity);
6357
6358
                    $item['maxScore'] = $exercise->get_max_score();
6359
6360
                    if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
6361
                        // Backwards compatibility with 1.9.x use mastery_score as min value
6362
                        $selectedMinScoreValue = $masteryScoreAsMinValue;
6363
                    }
6364
                    $return .= '<td>';
6365
                    $return .= '<input
6366
                        class="form-control"
6367
                        size="4" maxlength="3"
6368
                        name="min_'.$itemId.'"
6369
                        type="number"
6370
                        min="0"
6371
                        step="any"
6372
                        max="'.$item['maxScore'].'"
6373
                        value="'.$selectedMinScoreValue.'"
6374
                    />';
6375
                    $return .= '</td>';
6376
                    $return .= '<td>';
6377
                    $return .= '<input
6378
                        class="form-control"
6379
                        size="4"
6380
                        maxlength="3"
6381
                        name="max_'.$itemId.'"
6382
                        type="number"
6383
                        min="0"
6384
                        step="any"
6385
                        max="'.$item['maxScore'].'"
6386
                        value="'.$selectedMaxScoreValue.'"
6387
                    />';
6388
                        $return .= '</td>';
6389
                    }
6390
6391
                if (TOOL_HOTPOTATOES == $type) {
6392
                    $return .= '<td>';
6393
                    $return .= '<input
6394
                        size="4"
6395
                        maxlength="3"
6396
                        name="min_'.$itemId.'"
6397
                        type="number"
6398
                        min="0"
6399
                        step="any"
6400
                        max="'.$item['maxScore'].'"
6401
                        value="'.$selectedMinScoreValue.'"
6402
                    />';
6403
                        $return .= '</td>';
6404
                        $return .= '<td>';
6405
                        $return .= '<input
6406
                        size="4"
6407
                        maxlength="3"
6408
                        name="max_'.$itemId.'"
6409
                        type="number"
6410
                        min="0"
6411
                        step="any"
6412
                        max="'.$item['maxScore'].'"
6413
                        value="'.$selectedMaxScoreValue.'"
6414
                    />';
6415
                    $return .= '</td>';
6416
                }
6417
                $return .= '</tr>';
6418
6419
                return $return;
6420
            },
6421
        ];
6422
6423
        $tree = $lpItemRepo->childrenHierarchy($itemRoot, false, $options);
6424
        $return .= $tree;
6425
        $return .= '</tbody>';
6426
        $return .= '</table>';
6427
        $return .= '</div>';
6428
        $return .= '<div class="form-group">';
6429
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
6430
            get_lang('Save prerequisites settings').'</button>';
6431
        $return .= '</form>';
6432
6433
        return $return;
6434
    }
6435
6436
    /**
6437
     * Return HTML list to allow prerequisites selection for lp.
6438
     */
6439
    public function display_lp_prerequisites_list(FormValidator $form)
6440
    {
6441
        $lp_id = $this->lp_id;
6442
        $lp = api_get_lp_entity($lp_id);
6443
        $prerequisiteId = $lp->getPrerequisite();
6444
6445
        $repo = Container::getLpRepository();
6446
        $qb = $repo->findAllByCourse(api_get_course_entity(), api_get_session_entity());
6447
        /** @var CLp[] $lps */
6448
        $lps = $qb->getQuery()->getResult();
6449
6450
        //$session_id = api_get_session_id();
6451
        /*$session_condition = api_get_session_condition($session_id, true, true);
6452
        $sql = "SELECT * FROM $tbl_lp
6453
                WHERE c_id = $course_id $session_condition
6454
                ORDER BY display_order ";
6455
        $rs = Database::query($sql);*/
6456
6457
        $items = [get_lang('none')];
6458
        foreach ($lps as $lp) {
6459
            $myLpId = $lp->getIid();
6460
            if ($myLpId == $lp_id) {
6461
                continue;
6462
            }
6463
            $items[$myLpId] = $lp->getName();
6464
            /*$return .= '<option
6465
                value="'.$myLpId.'" '.(($myLpId == $prerequisiteId) ? ' selected ' : '').'>'.
6466
                $lp->getName().
6467
                '</option>';*/
6468
        }
6469
6470
        $select = $form->addSelect('prerequisites', get_lang('Prerequisites'), $items);
6471
        $select->setSelected($prerequisiteId);
6472
    }
6473
6474
    /**
6475
     * Creates a list with all the documents in it.
6476
     *
6477
     * @param bool $showInvisibleFiles
6478
     *
6479
     * @throws Exception
6480
     *
6481
     *
6482
     * @return string
6483
     */
6484
    public function get_documents($showInvisibleFiles = false)
6485
    {
6486
        $sessionId = api_get_session_id();
6487
        $documentTree = DocumentManager::get_document_preview(
6488
            api_get_course_entity(),
6489
            $this->lp_id,
6490
            null,
6491
            $sessionId,
6492
            true,
6493
            null,
6494
            null,
6495
            $showInvisibleFiles,
6496
            true
6497
        );
6498
6499
        $form = new FormValidator(
6500
            'form_upload',
6501
            'POST',
6502
            $this->getCurrentBuildingModeURL(),
6503
            '',
6504
            ['enctype' => 'multipart/form-data']
6505
        );
6506
6507
        $folders = DocumentManager::get_all_document_folders(
6508
            api_get_course_info(),
6509
            0,
6510
            true
6511
        );
6512
6513
        $folder = $this->generate_lp_folder(api_get_course_info());
6514
6515
        DocumentManager::build_directory_selector(
6516
            $folders,
6517
            $folder->getIid(),
6518
            [],
6519
            true,
6520
            $form,
6521
            'directory_parent_id'
6522
        );
6523
6524
        $group = [
6525
            $form->createElement(
6526
                'radio',
6527
                'if_exists',
6528
                get_lang('If file exists:'),
6529
                get_lang('Do nothing'),
6530
                'nothing'
6531
            ),
6532
            $form->createElement(
6533
                'radio',
6534
                'if_exists',
6535
                null,
6536
                get_lang('Overwrite the existing file'),
6537
                'overwrite'
6538
            ),
6539
            $form->createElement(
6540
                'radio',
6541
                'if_exists',
6542
                null,
6543
                get_lang('Rename the uploaded file if it exists'),
6544
                'rename'
6545
            ),
6546
        ];
6547
        $form->addGroup($group, null, get_lang('If file exists:'));
6548
6549
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
6550
        $defaultFileExistsOption = 'rename';
6551
        if (!empty($fileExistsOption)) {
6552
            $defaultFileExistsOption = $fileExistsOption;
6553
        }
6554
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
6555
6556
        // Check box options
6557
        $form->addCheckBox(
6558
            'unzip',
6559
            get_lang('Options'),
6560
            get_lang('Uncompress zip')
6561
        );
6562
6563
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
6564
        $form->addMultipleUpload($url);
6565
6566
        $lpItem = (new CLpItem())
6567
            ->setTitle('')
6568
            ->setItemType(TOOL_DOCUMENT)
6569
        ;
6570
        $new = $this->displayDocumentForm('add', $lpItem);
6571
6572
        /*$lpItem = new CLpItem();
6573
        $lpItem->setItemType(TOOL_READOUT_TEXT);
6574
        $frmReadOutText = $this->displayDocumentForm('add');*/
6575
6576
        $headers = [
6577
            get_lang('Files'),
6578
            get_lang('Create a new document'),
6579
            //get_lang('Create read-out text'),
6580
            get_lang('Upload'),
6581
        ];
6582
6583
        return Display::tabs(
6584
            $headers,
6585
            [$documentTree, $new, $form->returnForm()],
6586
            'subtab'
6587
        );
6588
    }
6589
6590
    /**
6591
     * Creates a list with all the exercises (quiz) in it.
6592
     *
6593
     * @return string
6594
     */
6595
    public function get_exercises()
6596
    {
6597
        $course_id = api_get_course_int_id();
6598
        $session_id = api_get_session_id();
6599
        $setting = 'true' === api_get_setting('lp.show_invisible_exercise_in_lp_toc');
6600
6601
        //$activeCondition = ' active <> -1 ';
6602
        $active = 2;
6603
        if ($setting) {
6604
            $active = 1;
6605
            //$activeCondition = ' active = 1 ';
6606
        }
6607
6608
        $categoryCondition = '';
6609
6610
        $keyword = $_REQUEST['keyword'] ?? null;
6611
        $categoryId = $_REQUEST['category_id'] ?? null;
6612
        /*if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
6613
            $categoryCondition = " AND exercise_category_id = $categoryId ";
6614
        }
6615
6616
        $keywordCondition = '';
6617
6618
        if (!empty($keyword)) {
6619
            $keyword = Database::escape_string($keyword);
6620
            $keywordCondition = " AND title LIKE '%$keyword%' ";
6621
        }
6622
        */
6623
        $course = api_get_course_entity($course_id);
6624
        $session = api_get_session_entity($session_id);
6625
6626
        $qb = Container::getQuizRepository()->findAllByCourse($course, $session, $keyword, $active, false, $categoryId);
6627
        /** @var CQuiz[] $exercises */
6628
        $exercises = $qb->getQuery()->getResult();
6629
6630
        /*$sql_quiz = "SELECT * FROM $tbl_quiz
6631
                     WHERE
6632
                            c_id = $course_id AND
6633
                            $activeCondition
6634
                            $condition_session
6635
                            $categoryCondition
6636
                            $keywordCondition
6637
                     ORDER BY title ASC";
6638
        $res_quiz = Database::query($sql_quiz);*/
6639
6640
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
6641
6642
        // Create a search-box
6643
        /*$form = new FormValidator('search_simple', 'get', $currentUrl);
6644
        $form->addHidden('action', 'add_item');
6645
        $form->addHidden('type', 'step');
6646
        $form->addHidden('lp_id', $this->lp_id);
6647
        $form->addHidden('lp_build_selected', '2');
6648
6649
        $form->addCourseHiddenParams();
6650
        $form->addText(
6651
            'keyword',
6652
            get_lang('Search'),
6653
            false,
6654
            [
6655
                'aria-label' => get_lang('Search'),
6656
            ]
6657
        );
6658
6659
        if (api_get_configuration_value('allow_exercise_categories')) {
6660
            $manager = new ExerciseCategoryManager();
6661
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
6662
            if (!empty($options)) {
6663
                $form->addSelect(
6664
                    'category_id',
6665
                    get_lang('Category'),
6666
                    $options,
6667
                    ['placeholder' => get_lang('Please select an option')]
6668
                );
6669
            }
6670
        }
6671
6672
        $form->addButtonSearch(get_lang('Search'));
6673
        $return = $form->returnForm();*/
6674
6675
        $return = '<ul class = "list-group lp_resource">';
6676
        $return .= '<li class="list-group-item lp_resource_element disable_drag">';
6677
        $return .= Display::return_icon('new_exercice.png');
6678
        $return .= '<a
6679
            href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
6680
            get_lang('New test').'</a>';
6681
        $return .= '</li>';
6682
6683
        $previewIcon = Display::return_icon(
6684
            'preview_view.png',
6685
            get_lang('Preview')
6686
        );
6687
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
6688
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
6689
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
6690
        foreach ($exercises as $exercise) {
6691
            $exerciseId = $exercise->getIid();
6692
            $title = strip_tags(api_html_entity_decode($exercise->getTitle()));
6693
            $visibility = $exercise->isVisible($course, $session);
6694
6695
            $link = Display::url(
6696
                $previewIcon,
6697
                $exerciseUrl.'&exerciseId='.$exerciseId,
6698
                ['target' => '_blank']
6699
            );
6700
            $return .= '<li
6701
                class="list-group-item lp_resource_element"
6702
                id="'.$exerciseId.'"
6703
                data-id="'.$exerciseId.'"
6704
                title="'.$title.'">';
6705
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
6706
            $return .= $quizIcon;
6707
            $sessionStar = '';
6708
            /*$sessionStar = api_get_session_image(
6709
                $row_quiz['session_id'],
6710
                $userInfo['status']
6711
            );*/
6712
            $return .= Display::url(
6713
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
6714
                api_get_self().'?'.
6715
                    api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$exerciseId.'&lp_id='.$this->lp_id,
6716
                [
6717
                    'class' => false === $visibility ? 'moved text-muted ' : 'moved link_with_id',
6718
                    'data_type' => 'quiz',
6719
                    'data-id' => $exerciseId,
6720
                ]
6721
            );
6722
            $return .= '</li>';
6723
        }
6724
6725
        $return .= '</ul>';
6726
6727
        return $return;
6728
    }
6729
6730
    /**
6731
     * Creates a list with all the links in it.
6732
     *
6733
     * @return string
6734
     */
6735
    public function get_links()
6736
    {
6737
        $sessionId = api_get_session_id();
6738
        $repo = Container::getLinkRepository();
6739
6740
        $course = api_get_course_entity();
6741
        $session = api_get_session_entity($sessionId);
6742
        $qb = $repo->getResourcesByCourse($course, $session);
6743
        /** @var CLink[] $links */
6744
        $links = $qb->getQuery()->getResult();
6745
6746
        $selfUrl = api_get_self();
6747
        $courseIdReq = api_get_cidreq();
6748
        $userInfo = api_get_user_info();
6749
6750
        $moveEverywhereIcon = Display::return_icon(
6751
            'move_everywhere.png',
6752
            get_lang('Move'),
6753
            [],
6754
            ICON_SIZE_TINY
6755
        );
6756
6757
        $categorizedLinks = [];
6758
        $categories = [];
6759
6760
        foreach ($links as $link) {
6761
            $categoryId = null !== $link->getCategory() ? $link->getCategory()->getIid() : 0;
6762
            if (empty($categoryId)) {
6763
                $categories[0] = get_lang('Uncategorized');
6764
            } else {
6765
                $category = $link->getCategory();
6766
                $categories[$categoryId] = $category->getCategoryTitle();
6767
            }
6768
            $categorizedLinks[$categoryId][$link->getIid()] = $link;
6769
        }
6770
6771
        $linksHtmlCode =
6772
            '<script>
6773
            function toggle_tool(tool, id) {
6774
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
6775
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
6776
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
6777
                } else {
6778
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
6779
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
6780
                }
6781
            }
6782
        </script>
6783
6784
        <ul class="list-group lp_resource">
6785
            <li class="list-group-item lp_resource_element disable_drag ">
6786
                '.Display::return_icon('linksnew.gif').'
6787
                <a
6788
                href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'"
6789
                title="'.get_lang('Add a link').'">'.
6790
                get_lang('Add a link').'
6791
                </a>
6792
            </li>';
6793
        $linkIcon = Display::return_icon('links.png', '', [], ICON_SIZE_TINY);
6794
        foreach ($categorizedLinks as $categoryId => $links) {
6795
            $linkNodes = null;
6796
            /** @var CLink $link */
6797
            foreach ($links as $key => $link) {
6798
                $title = $link->getTitle();
6799
                $id = $link->getIid();
6800
                $linkUrl = Display::url(
6801
                    Display::return_icon('preview_view.png', get_lang('Preview')),
6802
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
6803
                    ['target' => '_blank']
6804
                );
6805
6806
                if ($link->isVisible($course, $session)) {
6807
                    //$sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
6808
                    $sessionStar = '';
6809
                    $url = $selfUrl.'?'.$courseIdReq.'&action=add_item&type='.TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id;
6810
                    $link = Display::url(
6811
                        Security::remove_XSS($title).$sessionStar.$linkUrl,
6812
                        $url,
6813
                        [
6814
                            'class' => 'moved link_with_id',
6815
                            'data-id' => $key,
6816
                            'data_type' => TOOL_LINK,
6817
                            'title' => $title,
6818
                        ]
6819
                    );
6820
                    $linkNodes .=
6821
                        "<li
6822
                            class='list-group-item lp_resource_element'
6823
                            id= $id
6824
                            data-id= $id
6825
                            >
6826
                         <a class='moved' href='#'>
6827
                            $moveEverywhereIcon
6828
                        </a>
6829
                        $linkIcon $link
6830
                        </li>";
6831
                }
6832
            }
6833
            $linksHtmlCode .=
6834
                '<li class="list-group-item disable_drag">
6835
                    <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" >
6836
                        <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
6837
                        align="absbottom" />
6838
                    </a>
6839
                    <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
6840
                </li>
6841
            '.
6842
                $linkNodes.
6843
            '';
6844
            //<div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.
6845
        }
6846
        $linksHtmlCode .= '</ul>';
6847
6848
        return $linksHtmlCode;
6849
    }
6850
6851
    /**
6852
     * Creates a list with all the student publications in it.
6853
     *
6854
     * @return string
6855
     */
6856
    public function get_student_publications()
6857
    {
6858
        $return = '<ul class="list-group lp_resource">';
6859
        $return .= '<li class="list-group-item lp_resource_element">';
6860
        $works = getWorkListTeacher(0, 100, null, null, null);
6861
        if (!empty($works)) {
6862
            $icon = Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
6863
            foreach ($works as $work) {
6864
                $workId = $work['iid'];
6865
                $link = Display::url(
6866
                    Display::return_icon('preview_view.png', get_lang('Preview')),
6867
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$workId,
6868
                    ['target' => '_blank']
6869
                );
6870
6871
                $return .= '<li
6872
                    class="list-group-item lp_resource_element"
6873
                    id="'.$workId.'"
6874
                    data-id="'.$workId.'"
6875
                    >';
6876
                $return .= '<a class="moved" href="#">';
6877
                $return .= Display::return_icon(
6878
                    'move_everywhere.png',
6879
                    get_lang('Move'),
6880
                    [],
6881
                    ICON_SIZE_TINY
6882
                );
6883
                $return .= '</a> ';
6884
6885
                $return .= $icon;
6886
                $return .= Display::url(
6887
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link,
6888
                    api_get_self().'?'.
6889
                    api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&file='.$work['iid'].'&lp_id='.$this->lp_id,
6890
                    [
6891
                        'class' => 'moved link_with_id',
6892
                        'data-id' => $work['iid'],
6893
                        'data_type' => TOOL_STUDENTPUBLICATION,
6894
                        'title' => Security::remove_XSS(cut(strip_tags($work['title']), 80)),
6895
                    ]
6896
                );
6897
                $return .= '</li>';
6898
            }
6899
        }
6900
6901
        $return .= '</ul>';
6902
6903
        return $return;
6904
    }
6905
6906
    /**
6907
     * Creates a list with all the forums in it.
6908
     *
6909
     * @return string
6910
     */
6911
    public function get_forums()
6912
    {
6913
        $forumCategories = get_forum_categories();
6914
        $forumsInNoCategory = get_forums_in_category(0);
6915
        if (!empty($forumsInNoCategory)) {
6916
            $forumCategories = array_merge(
6917
                $forumCategories,
6918
                [
6919
                    [
6920
                        'cat_id' => 0,
6921
                        'session_id' => 0,
6922
                        'visibility' => 1,
6923
                        'cat_comment' => null,
6924
                    ],
6925
                ]
6926
            );
6927
        }
6928
6929
        $a_forums = [];
6930
        $courseEntity = api_get_course_entity(api_get_course_int_id());
6931
        $sessionEntity = api_get_session_entity(api_get_session_id());
6932
6933
        foreach ($forumCategories as $forumCategory) {
6934
            // The forums in this category.
6935
            $forumsInCategory = get_forums_in_category($forumCategory->getIid());
6936
            if (!empty($forumsInCategory)) {
6937
                foreach ($forumsInCategory as $forum) {
6938
                    if ($forum->isVisible($courseEntity, $sessionEntity)) {
6939
                        $a_forums[] = $forum;
6940
                    }
6941
                }
6942
            }
6943
        }
6944
6945
        $return = '<ul class="list-group lp_resource">';
6946
6947
        // First add link
6948
        $return .= '<li class="list-group-item lp_resource_element disable_drag">';
6949
        $return .= Display::return_icon('new_forum.png');
6950
        $return .= Display::url(
6951
            get_lang('Create a new forum'),
6952
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
6953
                'action' => 'add',
6954
                'content' => 'forum',
6955
                'lp_id' => $this->lp_id,
6956
            ]),
6957
            ['title' => get_lang('Create a new forum')]
6958
        );
6959
        $return .= '</li>';
6960
6961
        $return .= '<script>
6962
            function toggle_forum(forum_id) {
6963
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
6964
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
6965
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
6966
                } else {
6967
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
6968
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
6969
                }
6970
            }
6971
        </script>';
6972
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
6973
        foreach ($a_forums as $forum) {
6974
            $forumId = $forum->getIid();
6975
            $title = Security::remove_XSS($forum->getForumTitle());
6976
            $link = Display::url(
6977
                Display::return_icon('preview_view.png', get_lang('Preview')),
6978
                api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forumId,
6979
                ['target' => '_blank']
6980
            );
6981
6982
            $return .= '<li
6983
                    class="list-group-item lp_resource_element"
6984
                    id="'.$forumId.'"
6985
                    data-id="'.$forumId.'"
6986
                    >';
6987
            $return .= '<a class="moved" href="#">';
6988
            $return .= $moveIcon;
6989
            $return .= ' </a>';
6990
            $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
6991
6992
            $moveLink = Display::url(
6993
                $title.' '.$link,
6994
                api_get_self().'?'.
6995
                api_get_cidreq().'&action=add_item&type='.TOOL_FORUM.'&forum_id='.$forumId.'&lp_id='.$this->lp_id,
6996
                [
6997
                    'class' => 'moved link_with_id',
6998
                    'data-id' => $forumId,
6999
                    'data_type' => TOOL_FORUM,
7000
                    'title' => $title,
7001
                    'style' => 'vertical-align:middle',
7002
                ]
7003
            );
7004
            $return .= '<a onclick="javascript:toggle_forum('.$forumId.');" style="cursor:hand; vertical-align:middle">
7005
                            <img
7006
                                src="'.Display::returnIconPath('add.png').'"
7007
                                id="forum_'.$forumId.'_opener" align="absbottom"
7008
                             />
7009
                        </a>
7010
                        '.$moveLink;
7011
            $return .= '</li>';
7012
7013
            $return .= '<div style="display:none" id="forum_'.$forumId.'_content">';
7014
            $threads = get_threads($forumId);
7015
            if (is_array($threads)) {
7016
                foreach ($threads as $thread) {
7017
                    $threadId = $thread->getIid();
7018
                    $link = Display::url(
7019
                        Display::return_icon('preview_view.png', get_lang('Preview')),
7020
                        api_get_path(WEB_CODE_PATH).
7021
                        'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forumId.'&thread='.$threadId,
7022
                        ['target' => '_blank']
7023
                    );
7024
7025
                    $return .= '<li
7026
                        class="list-group-item lp_resource_element"
7027
                      id="'.$threadId.'"
7028
                        data-id="'.$threadId.'"
7029
                    >';
7030
                    $return .= '&nbsp;<a class="moved" href="#">';
7031
                    $return .= $moveIcon;
7032
                    $return .= ' </a>';
7033
                    $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
7034
                    $return .= '<a
7035
                        class="moved link_with_id"
7036
                        data-id="'.$threadId.'"
7037
                        data_type="'.TOOL_THREAD.'"
7038
                        title="'.$thread->getThreadTitle().'"
7039
                        href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_THREAD.'&thread_id='.$threadId.'&lp_id='.$this->lp_id.'"
7040
                        >'.
7041
                        Security::remove_XSS($thread->getThreadTitle()).' '.$link.'</a>';
7042
                    $return .= '</li>';
7043
                }
7044
            }
7045
            $return .= '</div>';
7046
        }
7047
        $return .= '</ul>';
7048
7049
        return $return;
7050
    }
7051
7052
    /**
7053
     * Temp function to be moved in main_api or the best place around for this.
7054
     * Creates a file path if it doesn't exist.
7055
     *
7056
     * @param string $path
7057
     */
7058
    public function create_path($path)
7059
    {
7060
        $path_bits = explode('/', dirname($path));
7061
7062
        // IS_WINDOWS_OS has been defined in main_api.lib.php
7063
        $path_built = IS_WINDOWS_OS ? '' : '/';
7064
        foreach ($path_bits as $bit) {
7065
            if (!empty($bit)) {
7066
                $new_path = $path_built.$bit;
7067
                if (is_dir($new_path)) {
7068
                    $path_built = $new_path.'/';
7069
                } else {
7070
                    mkdir($new_path, api_get_permissions_for_new_directories());
7071
                    $path_built = $new_path.'/';
7072
                }
7073
            }
7074
        }
7075
    }
7076
7077
    /**
7078
     * @param int    $lp_id
7079
     * @param string $status
7080
     */
7081
    public function set_autolaunch($lp_id, $status)
7082
    {
7083
        $course_id = api_get_course_int_id();
7084
        $lp_id = (int) $lp_id;
7085
        $status = (int) $status;
7086
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
7087
7088
        // Setting everything to autolaunch = 0
7089
        $attributes['autolaunch'] = 0;
7090
        $where = [
7091
            'session_id = ? AND c_id = ? ' => [
7092
                api_get_session_id(),
7093
                $course_id,
7094
            ],
7095
        ];
7096
        Database::update($lp_table, $attributes, $where);
7097
        if (1 == $status) {
7098
            //Setting my lp_id to autolaunch = 1
7099
            $attributes['autolaunch'] = 1;
7100
            $where = [
7101
                'iid = ? AND session_id = ? AND c_id = ?' => [
7102
                    $lp_id,
7103
                    api_get_session_id(),
7104
                    $course_id,
7105
                ],
7106
            ];
7107
            Database::update($lp_table, $attributes, $where);
7108
        }
7109
    }
7110
7111
    /**
7112
     * Gets previous_item_id for the next element of the lp_item table.
7113
     *
7114
     * @author Isaac flores paz
7115
     *
7116
     * @return int Previous item ID
7117
     */
7118
    public function select_previous_item_id()
7119
    {
7120
        $course_id = api_get_course_int_id();
7121
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7122
7123
        // Get the max order of the items
7124
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
7125
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7126
        $rs_max_order = Database::query($sql);
7127
        $row_max_order = Database::fetch_object($rs_max_order);
7128
        $max_order = $row_max_order->display_order;
7129
        // Get the previous item ID
7130
        $sql = "SELECT iid as previous FROM $table_lp_item
7131
                WHERE
7132
                    c_id = $course_id AND
7133
                    lp_id = ".$this->lp_id." AND
7134
                    display_order = '$max_order' ";
7135
        $rs_max = Database::query($sql);
7136
        $row_max = Database::fetch_object($rs_max);
7137
7138
        // Return the previous item ID
7139
        return $row_max->previous;
7140
    }
7141
7142
    /**
7143
     * Copies an LP.
7144
     */
7145
    public function copy()
7146
    {
7147
        // Course builder
7148
        $cb = new CourseBuilder();
7149
7150
        //Setting tools that will be copied
7151
        $cb->set_tools_to_build(['learnpaths']);
7152
7153
        //Setting elements that will be copied
7154
        $cb->set_tools_specific_id_list(
7155
            ['learnpaths' => [$this->lp_id]]
7156
        );
7157
7158
        $course = $cb->build();
7159
7160
        //Course restorer
7161
        $course_restorer = new CourseRestorer($course);
7162
        $course_restorer->set_add_text_in_items(true);
7163
        $course_restorer->set_tool_copy_settings(
7164
            ['learnpaths' => ['reset_dates' => true]]
7165
        );
7166
        $course_restorer->restore(
7167
            api_get_course_id(),
7168
            api_get_session_id(),
7169
            false,
7170
            false
7171
        );
7172
    }
7173
7174
    /**
7175
     * Verify document size.
7176
     *
7177
     * @param string $s
7178
     *
7179
     * @return bool
7180
     */
7181
    public static function verify_document_size($s)
7182
    {
7183
        $post_max = ini_get('post_max_size');
7184
        if ('M' == substr($post_max, -1, 1)) {
7185
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
7186
        } elseif ('G' == substr($post_max, -1, 1)) {
7187
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
7188
        }
7189
        $upl_max = ini_get('upload_max_filesize');
7190
        if ('M' == substr($upl_max, -1, 1)) {
7191
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
7192
        } elseif ('G' == substr($upl_max, -1, 1)) {
7193
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
7194
        }
7195
7196
        $repo = Container::getDocumentRepository();
7197
        $documents_total_space = $repo->getTotalSpace(api_get_course_int_id());
7198
7199
        $course_max_space = DocumentManager::get_course_quota();
7200
        $total_size = filesize($s) + $documents_total_space;
7201
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
7202
            return true;
7203
        }
7204
7205
        return false;
7206
    }
7207
7208
    /**
7209
     * Clear LP prerequisites.
7210
     */
7211
    public function clearPrerequisites()
7212
    {
7213
        $course_id = $this->get_course_int_id();
7214
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7215
        $lp_id = $this->get_id();
7216
        // Cleaning prerequisites
7217
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
7218
                WHERE lp_id = $lp_id";
7219
        Database::query($sql);
7220
7221
        // Cleaning mastery score for exercises
7222
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
7223
                WHERE lp_id = $lp_id AND item_type = 'quiz'";
7224
        Database::query($sql);
7225
    }
7226
7227
    public function set_previous_step_as_prerequisite_for_all_items()
7228
    {
7229
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7230
        $course_id = $this->get_course_int_id();
7231
        $lp_id = $this->get_id();
7232
7233
        if (!empty($this->items)) {
7234
            $previous_item_id = null;
7235
            $previous_item_max = 0;
7236
            $previous_item_type = null;
7237
            $last_item_not_dir = null;
7238
            $last_item_not_dir_type = null;
7239
            $last_item_not_dir_max = null;
7240
7241
            foreach ($this->ordered_items as $itemId) {
7242
                $item = $this->getItem($itemId);
7243
                // if there was a previous item... (otherwise jump to set it)
7244
                if (!empty($previous_item_id)) {
7245
                    $current_item_id = $item->get_id(); //save current id
7246
                    if ('dir' != $item->get_type()) {
7247
                        // Current item is not a folder, so it qualifies to get a prerequisites
7248
                        if ('quiz' == $last_item_not_dir_type) {
7249
                            // if previous is quiz, mark its max score as default score to be achieved
7250
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
7251
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
7252
                            Database::query($sql);
7253
                        }
7254
                        // now simply update the prerequisite to set it to the last non-chapter item
7255
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
7256
                                WHERE lp_id = $lp_id AND iid = $current_item_id";
7257
                        Database::query($sql);
7258
                        // record item as 'non-chapter' reference
7259
                        $last_item_not_dir = $item->get_id();
7260
                        $last_item_not_dir_type = $item->get_type();
7261
                        $last_item_not_dir_max = $item->get_max();
7262
                    }
7263
                } else {
7264
                    if ('dir' != $item->get_type()) {
7265
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
7266
                        $last_item_not_dir = $item->get_id();
7267
                        $last_item_not_dir_type = $item->get_type();
7268
                        $last_item_not_dir_max = $item->get_max();
7269
                    }
7270
                }
7271
                // Saving the item as "previous item" for the next loop
7272
                $previous_item_id = $item->get_id();
7273
                $previous_item_max = $item->get_max();
7274
                $previous_item_type = $item->get_type();
7275
            }
7276
        }
7277
    }
7278
7279
    /**
7280
     * @param array $params
7281
     *
7282
     * @return int
7283
     */
7284
    public static function createCategory($params)
7285
    {
7286
        $courseEntity = api_get_course_entity(api_get_course_int_id());
7287
7288
        $item = new CLpCategory();
7289
        $item
7290
            ->setName($params['name'])
7291
            ->setParent($courseEntity)
7292
            ->addCourseLink($courseEntity, api_get_session_entity())
7293
        ;
7294
7295
        $repo = Container::getLpCategoryRepository();
7296
        $repo->create($item);
7297
7298
        return $item->getIid();
7299
    }
7300
7301
    /**
7302
     * @param array $params
7303
     */
7304
    public static function updateCategory($params)
7305
    {
7306
        $em = Database::getManager();
7307
        /** @var CLpCategory $item */
7308
        $item = $em->find(CLpCategory::class, $params['id']);
7309
        if ($item) {
7310
            $item->setName($params['name']);
7311
            $em->persist($item);
7312
            $em->flush();
7313
        }
7314
    }
7315
7316
    /**
7317
     * @param int $id
7318
     */
7319
    public static function moveUpCategory($id)
7320
    {
7321
        $id = (int) $id;
7322
        $em = Database::getManager();
7323
        /** @var CLpCategory $item */
7324
        $item = $em->find(CLpCategory::class, $id);
7325
        if ($item) {
7326
            $position = $item->getPosition() - 1;
7327
            $item->setPosition($position);
7328
            $em->persist($item);
7329
            $em->flush();
7330
        }
7331
    }
7332
7333
    /**
7334
     * @param int $id
7335
     */
7336
    public static function moveDownCategory($id)
7337
    {
7338
        $id = (int) $id;
7339
        $em = Database::getManager();
7340
        /** @var CLpCategory $item */
7341
        $item = $em->find(CLpCategory::class, $id);
7342
        if ($item) {
7343
            $position = $item->getPosition() + 1;
7344
            $item->setPosition($position);
7345
            $em->persist($item);
7346
            $em->flush();
7347
        }
7348
    }
7349
7350
    /**
7351
     * @param int $courseId
7352
     *
7353
     * @return int
7354
     */
7355
    public static function getCountCategories($courseId)
7356
    {
7357
        if (empty($courseId)) {
7358
            return 0;
7359
        }
7360
        $repo = Container::getLpCategoryRepository();
7361
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId));
7362
        $qb->addSelect('count(resource)');
7363
7364
        return (int) $qb->getQuery()->getSingleScalarResult();
7365
    }
7366
7367
    /**
7368
     * @param int $courseId
7369
     *
7370
     * @return CLpCategory[]
7371
     */
7372
    public static function getCategories($courseId)
7373
    {
7374
        // Using doctrine extensions
7375
        $repo = Container::getLpCategoryRepository();
7376
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId));
7377
7378
        return $qb->getQuery()->getResult();
7379
    }
7380
7381
    public static function getCategorySessionId($id)
7382
    {
7383
        if (false === api_get_configuration_value('allow_session_lp_category')) {
7384
            return 0;
7385
        }
7386
7387
        $table = Database::get_course_table(TABLE_LP_CATEGORY);
7388
        $id = (int) $id;
7389
7390
        $sql = "SELECT session_id FROM $table WHERE iid = $id";
7391
        $result = Database::query($sql);
7392
        $result = Database::fetch_array($result, 'ASSOC');
7393
7394
        if ($result) {
7395
            return (int) $result['session_id'];
7396
        }
7397
7398
        return 0;
7399
    }
7400
7401
    /**
7402
     * @param int $id
7403
     */
7404
    public static function deleteCategory($id): bool
7405
    {
7406
        $repo = Container::getLpCategoryRepository();
7407
        /** @var CLpCategory $category */
7408
        $category = $repo->find($id);
7409
        if ($category) {
7410
            $em = Database::getManager();
7411
            $lps = $category->getLps();
7412
7413
            foreach ($lps as $lp) {
7414
                $lp->setCategory(null);
7415
                $em->persist($lp);
7416
            }
7417
7418
            // Removing category.
7419
            $em->remove($category);
7420
            $em->flush();
7421
7422
            return true;
7423
        }
7424
7425
        return false;
7426
    }
7427
7428
    /**
7429
     * @param int  $courseId
7430
     * @param bool $addSelectOption
7431
     *
7432
     * @return array
7433
     */
7434
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
7435
    {
7436
        $repo = Container::getLpCategoryRepository();
7437
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId));
7438
        $items = $qb->getQuery()->getResult();
7439
7440
        $cats = [];
7441
        if ($addSelectOption) {
7442
            $cats = [get_lang('Select a category')];
7443
        }
7444
7445
        if (!empty($items)) {
7446
            foreach ($items as $cat) {
7447
                $cats[$cat->getIid()] = $cat->getName();
7448
            }
7449
        }
7450
7451
        return $cats;
7452
    }
7453
7454
    /**
7455
     * @param string $courseCode
7456
     * @param int    $lpId
7457
     * @param int    $user_id
7458
     *
7459
     * @return learnpath
7460
     */
7461
    public static function getLpFromSession($courseCode, $lpId, $user_id)
7462
    {
7463
        $debug = 0;
7464
        $learnPath = null;
7465
        $lpObject = Session::read('lpobject');
7466
7467
        $repo = Container::getLpRepository();
7468
        $lp = $repo->find($lpId);
7469
        if (null !== $lpObject) {
7470
            /** @var learnpath $learnPath */
7471
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
7472
            $learnPath->entity = $lp;
7473
            if ($debug) {
7474
                error_log('getLpFromSession: unserialize');
7475
                error_log('------getLpFromSession------');
7476
                error_log('------unserialize------');
7477
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
7478
                error_log("api_get_sessionid: ".api_get_session_id());
7479
            }
7480
        }
7481
7482
        if (!is_object($learnPath)) {
7483
            $learnPath = new learnpath($lp, api_get_course_info($courseCode), $user_id);
7484
            if ($debug) {
7485
                error_log('------getLpFromSession------');
7486
                error_log('getLpFromSession: create new learnpath');
7487
                error_log("create new LP with $courseCode - $lpId - $user_id");
7488
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
7489
                error_log("api_get_sessionid: ".api_get_session_id());
7490
            }
7491
        }
7492
7493
        return $learnPath;
7494
    }
7495
7496
    /**
7497
     * @param int $itemId
7498
     *
7499
     * @return learnpathItem|false
7500
     */
7501
    public function getItem($itemId)
7502
    {
7503
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
7504
            return $this->items[$itemId];
7505
        }
7506
7507
        return false;
7508
    }
7509
7510
    /**
7511
     * @return int
7512
     */
7513
    public function getCurrentAttempt()
7514
    {
7515
        $attempt = $this->getItem($this->get_current_item_id());
7516
        if ($attempt) {
7517
            return $attempt->get_attempt_id();
7518
        }
7519
7520
        return 0;
7521
    }
7522
7523
    /**
7524
     * @return int
7525
     */
7526
    public function getCategoryId()
7527
    {
7528
        return (int) $this->categoryId;
7529
    }
7530
7531
    /**
7532
     * Get whether this is a learning path with the possibility to subscribe
7533
     * users or not.
7534
     *
7535
     * @return int
7536
     */
7537
    public function getSubscribeUsers()
7538
    {
7539
        return $this->subscribeUsers;
7540
    }
7541
7542
    /**
7543
     * Calculate the count of stars for a user in this LP
7544
     * This calculation is based on the following rules:
7545
     * - the student gets one star when he gets to 50% of the learning path
7546
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
7547
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
7548
     * - the student gets the final star when the score for the *last* test is >= 80%.
7549
     *
7550
     * @param int $sessionId Optional. The session ID
7551
     *
7552
     * @return int The count of stars
7553
     */
7554
    public function getCalculateStars($sessionId = 0)
7555
    {
7556
        $stars = 0;
7557
        $progress = self::getProgress(
7558
            $this->lp_id,
7559
            $this->user_id,
7560
            $this->course_int_id,
7561
            $sessionId
7562
        );
7563
7564
        if ($progress >= 50) {
7565
            $stars++;
7566
        }
7567
7568
        // Calculate stars chapters evaluation
7569
        $exercisesItems = $this->getExercisesItems();
7570
7571
        if (!empty($exercisesItems)) {
7572
            $totalResult = 0;
7573
7574
            foreach ($exercisesItems as $exerciseItem) {
7575
                $exerciseResultInfo = Event::getExerciseResultsByUser(
7576
                    $this->user_id,
7577
                    $exerciseItem->path,
7578
                    $this->course_int_id,
7579
                    $sessionId,
7580
                    $this->lp_id,
7581
                    $exerciseItem->db_id
7582
                );
7583
7584
                $exerciseResultInfo = end($exerciseResultInfo);
7585
7586
                if (!$exerciseResultInfo) {
7587
                    continue;
7588
                }
7589
7590
                if (!empty($exerciseResultInfo['max_score'])) {
7591
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
7592
                } else {
7593
                    $exerciseResult = 0;
7594
                }
7595
                $totalResult += $exerciseResult;
7596
            }
7597
7598
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
7599
7600
            if ($totalExerciseAverage >= 50) {
7601
                $stars++;
7602
            }
7603
7604
            if ($totalExerciseAverage >= 80) {
7605
                $stars++;
7606
            }
7607
        }
7608
7609
        // Calculate star for final evaluation
7610
        $finalEvaluationItem = $this->getFinalEvaluationItem();
7611
7612
        if (!empty($finalEvaluationItem)) {
7613
            $evaluationResultInfo = Event::getExerciseResultsByUser(
7614
                $this->user_id,
7615
                $finalEvaluationItem->path,
7616
                $this->course_int_id,
7617
                $sessionId,
7618
                $this->lp_id,
7619
                $finalEvaluationItem->db_id
7620
            );
7621
7622
            $evaluationResultInfo = end($evaluationResultInfo);
7623
7624
            if ($evaluationResultInfo) {
7625
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
7626
                if ($evaluationResult >= 80) {
7627
                    $stars++;
7628
                }
7629
            }
7630
        }
7631
7632
        return $stars;
7633
    }
7634
7635
    /**
7636
     * Get the items of exercise type.
7637
     *
7638
     * @return array The items. Otherwise return false
7639
     */
7640
    public function getExercisesItems()
7641
    {
7642
        $exercises = [];
7643
        foreach ($this->items as $item) {
7644
            if ('quiz' !== $item->type) {
7645
                continue;
7646
            }
7647
            $exercises[] = $item;
7648
        }
7649
7650
        array_pop($exercises);
7651
7652
        return $exercises;
7653
    }
7654
7655
    /**
7656
     * Get the item of exercise type (evaluation type).
7657
     *
7658
     * @return array The final evaluation. Otherwise return false
7659
     */
7660
    public function getFinalEvaluationItem()
7661
    {
7662
        $exercises = [];
7663
        foreach ($this->items as $item) {
7664
            if (TOOL_QUIZ !== $item->type) {
7665
                continue;
7666
            }
7667
7668
            $exercises[] = $item;
7669
        }
7670
7671
        return array_pop($exercises);
7672
    }
7673
7674
    /**
7675
     * Calculate the total points achieved for the current user in this learning path.
7676
     *
7677
     * @param int $sessionId Optional. The session Id
7678
     *
7679
     * @return int
7680
     */
7681
    public function getCalculateScore($sessionId = 0)
7682
    {
7683
        // Calculate stars chapters evaluation
7684
        $exercisesItems = $this->getExercisesItems();
7685
        $finalEvaluationItem = $this->getFinalEvaluationItem();
7686
        $totalExercisesResult = 0;
7687
        $totalEvaluationResult = 0;
7688
7689
        if (false !== $exercisesItems) {
7690
            foreach ($exercisesItems as $exerciseItem) {
7691
                $exerciseResultInfo = Event::getExerciseResultsByUser(
7692
                    $this->user_id,
7693
                    $exerciseItem->path,
7694
                    $this->course_int_id,
7695
                    $sessionId,
7696
                    $this->lp_id,
7697
                    $exerciseItem->db_id
7698
                );
7699
7700
                $exerciseResultInfo = end($exerciseResultInfo);
7701
7702
                if (!$exerciseResultInfo) {
7703
                    continue;
7704
                }
7705
7706
                $totalExercisesResult += $exerciseResultInfo['score'];
7707
            }
7708
        }
7709
7710
        if (!empty($finalEvaluationItem)) {
7711
            $evaluationResultInfo = Event::getExerciseResultsByUser(
7712
                $this->user_id,
7713
                $finalEvaluationItem->path,
7714
                $this->course_int_id,
7715
                $sessionId,
7716
                $this->lp_id,
7717
                $finalEvaluationItem->db_id
7718
            );
7719
7720
            $evaluationResultInfo = end($evaluationResultInfo);
7721
7722
            if ($evaluationResultInfo) {
7723
                $totalEvaluationResult += $evaluationResultInfo['score'];
7724
            }
7725
        }
7726
7727
        return $totalExercisesResult + $totalEvaluationResult;
7728
    }
7729
7730
    /**
7731
     * Check if URL is not allowed to be show in a iframe.
7732
     *
7733
     * @param string $src
7734
     *
7735
     * @return string
7736
     */
7737
    public function fixBlockedLinks($src)
7738
    {
7739
        $urlInfo = parse_url($src);
7740
7741
        $platformProtocol = 'https';
7742
        if (false === strpos(api_get_path(WEB_CODE_PATH), 'https')) {
7743
            $platformProtocol = 'http';
7744
        }
7745
7746
        $protocolFixApplied = false;
7747
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
7748
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
7749
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
7750
7751
        if ($platformProtocol != $scheme) {
7752
            Session::write('x_frame_source', $src);
7753
            $src = 'blank.php?error=x_frames_options';
7754
            $protocolFixApplied = true;
7755
        }
7756
7757
        if (false == $protocolFixApplied) {
7758
            if (false === strpos(api_get_path(WEB_PATH), $host)) {
7759
                // Check X-Frame-Options
7760
                $ch = curl_init();
7761
                $options = [
7762
                    CURLOPT_URL => $src,
7763
                    CURLOPT_RETURNTRANSFER => true,
7764
                    CURLOPT_HEADER => true,
7765
                    CURLOPT_FOLLOWLOCATION => true,
7766
                    CURLOPT_ENCODING => "",
7767
                    CURLOPT_AUTOREFERER => true,
7768
                    CURLOPT_CONNECTTIMEOUT => 120,
7769
                    CURLOPT_TIMEOUT => 120,
7770
                    CURLOPT_MAXREDIRS => 10,
7771
                ];
7772
7773
                $proxySettings = api_get_configuration_value('proxy_settings');
7774
                if (!empty($proxySettings) &&
7775
                    isset($proxySettings['curl_setopt_array'])
7776
                ) {
7777
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
7778
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
7779
                }
7780
7781
                curl_setopt_array($ch, $options);
7782
                $response = curl_exec($ch);
7783
                $httpCode = curl_getinfo($ch);
7784
                $headers = substr($response, 0, $httpCode['header_size']);
7785
7786
                $error = false;
7787
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
7788
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
7789
                ) {
7790
                    $error = true;
7791
                }
7792
7793
                if ($error) {
7794
                    Session::write('x_frame_source', $src);
7795
                    $src = 'blank.php?error=x_frames_options';
7796
                }
7797
            }
7798
        }
7799
7800
        return $src;
7801
    }
7802
7803
    /**
7804
     * Check if this LP has a created forum in the basis course.
7805
     *
7806
     * @deprecated
7807
     *
7808
     * @return bool
7809
     */
7810
    public function lpHasForum()
7811
    {
7812
        $forumTable = Database::get_course_table(TABLE_FORUM);
7813
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
7814
7815
        $fakeFrom = "
7816
            $forumTable f
7817
            INNER JOIN $itemProperty ip
7818
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
7819
        ";
7820
7821
        $resultData = Database::select(
7822
            'COUNT(f.iid) AS qty',
7823
            $fakeFrom,
7824
            [
7825
                'where' => [
7826
                    'ip.visibility != ? AND ' => 2,
7827
                    'ip.tool = ? AND ' => TOOL_FORUM,
7828
                    'f.c_id = ? AND ' => intval($this->course_int_id),
7829
                    'f.lp_id = ?' => intval($this->lp_id),
7830
                ],
7831
            ],
7832
            'first'
7833
        );
7834
7835
        return $resultData['qty'] > 0;
7836
    }
7837
7838
    /**
7839
     * Get the forum for this learning path.
7840
     *
7841
     * @param int $sessionId
7842
     *
7843
     * @return array
7844
     */
7845
    public function getForum($sessionId = 0)
7846
    {
7847
        $repo = Container::getForumRepository();
7848
7849
        $course = api_get_course_entity();
7850
        $session = api_get_session_entity($sessionId);
7851
        $qb = $repo->getResourcesByCourse($course, $session);
7852
7853
        return $qb->getQuery()->getResult();
7854
    }
7855
7856
    /**
7857
     * Get the LP Final Item form.
7858
     *
7859
     * @throws Exception
7860
     *
7861
     *
7862
     * @return string
7863
     */
7864
    public function getFinalItemForm()
7865
    {
7866
        $finalItem = $this->getFinalItem();
7867
        $title = '';
7868
7869
        if ($finalItem) {
7870
            $title = $finalItem->get_title();
7871
            $buttonText = get_lang('Save');
7872
            $content = $this->getSavedFinalItem();
7873
        } else {
7874
            $buttonText = get_lang('Add this document to the course');
7875
            $content = $this->getFinalItemTemplate();
7876
        }
7877
7878
        $editorConfig = [
7879
            'ToolbarSet' => 'LearningPathDocuments',
7880
            'Width' => '100%',
7881
            'Height' => '500',
7882
            'FullPage' => true,
7883
//            'CreateDocumentDir' => $relative_prefix,
7884
    //            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
7885
  //          'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
7886
        ];
7887
7888
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
7889
            'type' => 'document',
7890
            'lp_id' => $this->lp_id,
7891
        ]);
7892
7893
        $form = new FormValidator('final_item', 'POST', $url);
7894
        $form->addText('title', get_lang('Title'));
7895
        $form->addButtonSave($buttonText);
7896
        $form->addHtml(
7897
            Display::return_message(
7898
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
7899
                'normal',
7900
                false
7901
            )
7902
        );
7903
7904
        $renderer = $form->defaultRenderer();
7905
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
7906
7907
        $form->addHtmlEditor(
7908
            'content_lp_certificate',
7909
            null,
7910
            true,
7911
            false,
7912
            $editorConfig
7913
        );
7914
        $form->addHidden('action', 'add_final_item');
7915
        $form->addHidden('path', Session::read('pathItem'));
7916
        $form->addHidden('previous', $this->get_last());
7917
        $form->setDefaults(
7918
            ['title' => $title, 'content_lp_certificate' => $content]
7919
        );
7920
7921
        if ($form->validate()) {
7922
            $values = $form->exportValues();
7923
            $lastItemId = $this->getLastInFirstLevel();
7924
7925
            if (!$finalItem) {
7926
                $documentId = $this->create_document(
7927
                    $this->course_info,
7928
                    $values['content_lp_certificate'],
7929
                    $values['title']
7930
                );
7931
                $this->add_item(
7932
                    0,
7933
                    $lastItemId,
7934
                    'final_item',
7935
                    $documentId,
7936
                    $values['title'],
7937
                );
7938
7939
                Display::addFlash(
7940
                    Display::return_message(get_lang('Added'))
7941
                );
7942
            } else {
7943
                $this->edit_document($this->course_info);
7944
            }
7945
        }
7946
7947
        return $form->returnForm();
7948
    }
7949
7950
    /**
7951
     * Check if the current lp item is first, both, last or none from lp list.
7952
     *
7953
     * @param int $currentItemId
7954
     *
7955
     * @return string
7956
     */
7957
    public function isFirstOrLastItem($currentItemId)
7958
    {
7959
        $lpItemId = [];
7960
        $typeListNotToVerify = self::getChapterTypes();
7961
7962
        // Using get_toc() function instead $this->items because returns the correct order of the items
7963
        foreach ($this->get_toc() as $item) {
7964
            if (!in_array($item['type'], $typeListNotToVerify)) {
7965
                $lpItemId[] = $item['id'];
7966
            }
7967
        }
7968
7969
        $lastLpItemIndex = count($lpItemId) - 1;
7970
        $position = array_search($currentItemId, $lpItemId);
7971
7972
        switch ($position) {
7973
            case 0:
7974
                if (!$lastLpItemIndex) {
7975
                    $answer = 'both';
7976
                    break;
7977
                }
7978
7979
                $answer = 'first';
7980
                break;
7981
            case $lastLpItemIndex:
7982
                $answer = 'last';
7983
                break;
7984
            default:
7985
                $answer = 'none';
7986
        }
7987
7988
        return $answer;
7989
    }
7990
7991
    /**
7992
     * Get whether this is a learning path with the accumulated SCORM time or not.
7993
     *
7994
     * @return int
7995
     */
7996
    public function getAccumulateScormTime()
7997
    {
7998
        return $this->accumulateScormTime;
7999
    }
8000
8001
    /**
8002
     * Returns an HTML-formatted link to a resource, to incorporate directly into
8003
     * the new learning path tool.
8004
     *
8005
     * The function is a big switch on tool type.
8006
     * In each case, we query the corresponding table for information and build the link
8007
     * with that information.
8008
     *
8009
     * @author Yannick Warnier <[email protected]> - rebranding based on
8010
     * previous work (display_addedresource_link_in_learnpath())
8011
     *
8012
     * @param int $course_id      Course code
8013
     * @param int $learningPathId The learning path ID (in lp table)
8014
     * @param int $id_in_path     the unique index in the items table
8015
     * @param int $lpViewId
8016
     *
8017
     * @return string
8018
     */
8019
    public static function rl_get_resource_link_for_learnpath(
8020
        $course_id,
8021
        $learningPathId,
8022
        $id_in_path,
8023
        $lpViewId
8024
    ) {
8025
        $session_id = api_get_session_id();
8026
8027
        $learningPathId = (int) $learningPathId;
8028
        $id_in_path = (int) $id_in_path;
8029
        $lpViewId = (int) $lpViewId;
8030
8031
        $em = Database::getManager();
8032
        $lpItemRepo = $em->getRepository(CLpItem::class);
8033
8034
        /** @var CLpItem $rowItem */
8035
        $rowItem = $lpItemRepo->findOneBy([
8036
            'lp' => $learningPathId,
8037
            'iid' => $id_in_path,
8038
        ]);
8039
        $type = $rowItem->getItemType();
8040
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
8041
        $main_dir_path = api_get_path(WEB_CODE_PATH);
8042
        $link = '';
8043
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&sid='.$session_id;
8044
8045
        switch ($type) {
8046
            case 'dir':
8047
                return $main_dir_path.'lp/blank.php';
8048
            case TOOL_CALENDAR_EVENT:
8049
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
8050
            case TOOL_ANNOUNCEMENT:
8051
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
8052
            case TOOL_LINK:
8053
                $linkInfo = Link::getLinkInfo($id);
8054
                if (isset($linkInfo['url'])) {
8055
                    return $linkInfo['url'];
8056
                }
8057
8058
                return '';
8059
            case TOOL_QUIZ:
8060
                if (empty($id)) {
8061
                    return '';
8062
                }
8063
8064
                // Get the lp_item_view with the highest view_count.
8065
                $learnpathItemViewResult = $em
8066
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
8067
                    ->findBy(
8068
                        ['item' => $rowItem->getIid(), 'view' => $lpViewId],
8069
                        ['viewCount' => 'DESC'],
8070
                        1
8071
                    );
8072
                /** @var CLpItemView $learnpathItemViewData */
8073
                $learnpathItemViewData = current($learnpathItemViewResult);
8074
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getIid() : 0;
8075
8076
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
8077
                    .http_build_query([
8078
                        'lp_init' => 1,
8079
                        'learnpath_item_view_id' => $learnpathItemViewId,
8080
                        'learnpath_id' => $learningPathId,
8081
                        'learnpath_item_id' => $id_in_path,
8082
                        'exerciseId' => $id,
8083
                    ]);
8084
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
8085
                /*$TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
8086
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
8087
                $myrow = Database::fetch_array($result);
8088
                $path = $myrow['path'];
8089
8090
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
8091
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
8092
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;*/
8093
            case TOOL_FORUM:
8094
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
8095
            case TOOL_THREAD:
8096
                // forum post
8097
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
8098
                if (empty($id)) {
8099
                    return '';
8100
                }
8101
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND iid=$id";
8102
                $result = Database::query($sql);
8103
                $myrow = Database::fetch_array($result);
8104
8105
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
8106
                    .$extraParams;
8107
            case TOOL_POST:
8108
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
8109
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
8110
                $myrow = Database::fetch_array($result);
8111
8112
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
8113
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
8114
            case TOOL_READOUT_TEXT:
8115
                return api_get_path(WEB_CODE_PATH).
8116
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
8117
            case TOOL_DOCUMENT:
8118
                $repo = Container::getDocumentRepository();
8119
                $document = $repo->find($rowItem->getPath());
8120
                if ($document) {
8121
                    $params = [
8122
                        'cid' => $course_id,
8123
                        'sid' => $session_id,
8124
                    ];
8125
8126
                    return $repo->getResourceFileUrl($document, $params, UrlGeneratorInterface::ABSOLUTE_URL);
8127
                }
8128
8129
                return null;
8130
8131
                $documentPathInfo = pathinfo($document->getPath());
0 ignored issues
show
Unused Code introduced by
$documentPathInfo = path...o($document->getPath()) is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
8132
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v'];
8133
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
8134
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
8135
8136
                $openmethod = 2;
8137
                $officedoc = false;
8138
                Session::write('openmethod', $openmethod);
8139
                Session::write('officedoc', $officedoc);
8140
8141
                if ($showDirectUrl) {
8142
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
8143
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
8144
                        if (Link::isPdfLink($file)) {
8145
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
8146
8147
                            return $pdfUrl;
8148
                        }
8149
                    }
8150
8151
                    return $file;
8152
                }
8153
8154
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
8155
            case TOOL_LP_FINAL_ITEM:
8156
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
8157
                    .$extraParams;
8158
            case 'assignments':
8159
                return $main_dir_path.'work/work.php?'.$extraParams;
8160
            case TOOL_DROPBOX:
8161
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
8162
            case 'introduction_text': //DEPRECATED
8163
                return '';
8164
            case TOOL_COURSE_DESCRIPTION:
8165
                return $main_dir_path.'course_description?'.$extraParams;
8166
            case TOOL_GROUP:
8167
                return $main_dir_path.'group/group.php?'.$extraParams;
8168
            case TOOL_USER:
8169
                return $main_dir_path.'user/user.php?'.$extraParams;
8170
            case TOOL_STUDENTPUBLICATION:
8171
                if (!empty($rowItem->getPath())) {
8172
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
8173
                }
8174
8175
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
8176
        }
8177
8178
        return $link;
8179
    }
8180
8181
    /**
8182
     * Gets the name of a resource (generally used in learnpath when no name is provided).
8183
     *
8184
     * @author Yannick Warnier <[email protected]>
8185
     *
8186
     * @param string $course_code    Course code
8187
     * @param int    $learningPathId
8188
     * @param int    $id_in_path     The resource ID
8189
     *
8190
     * @return string
8191
     */
8192
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
8193
    {
8194
        $_course = api_get_course_info($course_code);
8195
        if (empty($_course)) {
8196
            return '';
8197
        }
8198
        $course_id = $_course['real_id'];
8199
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8200
        $learningPathId = (int) $learningPathId;
8201
        $id_in_path = (int) $id_in_path;
8202
8203
        $sql = "SELECT item_type, title, ref
8204
                FROM $tbl_lp_item
8205
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
8206
        $res_item = Database::query($sql);
8207
8208
        if (Database::num_rows($res_item) < 1) {
8209
            return '';
8210
        }
8211
        $row_item = Database::fetch_array($res_item);
8212
        $type = strtolower($row_item['item_type']);
8213
        $id = $row_item['ref'];
8214
        $output = '';
8215
8216
        switch ($type) {
8217
            case TOOL_CALENDAR_EVENT:
8218
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
8219
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
8220
                $myrow = Database::fetch_array($result);
8221
                $output = $myrow['title'];
8222
                break;
8223
            case TOOL_ANNOUNCEMENT:
8224
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
8225
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
8226
                $myrow = Database::fetch_array($result);
8227
                $output = $myrow['title'];
8228
                break;
8229
            case TOOL_LINK:
8230
                // Doesn't take $target into account.
8231
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
8232
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
8233
                $myrow = Database::fetch_array($result);
8234
                $output = $myrow['title'];
8235
                break;
8236
            case TOOL_QUIZ:
8237
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
8238
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
8239
                $myrow = Database::fetch_array($result);
8240
                $output = $myrow['title'];
8241
                break;
8242
            case TOOL_FORUM:
8243
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
8244
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
8245
                $myrow = Database::fetch_array($result);
8246
                $output = $myrow['forum_name'];
8247
                break;
8248
            case TOOL_THREAD:
8249
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
8250
                // Grabbing the title of the post.
8251
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
8252
                $result_title = Database::query($sql_title);
8253
                $myrow_title = Database::fetch_array($result_title);
8254
                $output = $myrow_title['post_title'];
8255
                break;
8256
            case TOOL_POST:
8257
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
8258
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
8259
                $result = Database::query($sql);
8260
                $post = Database::fetch_array($result);
8261
                $output = $post['post_title'];
8262
                break;
8263
            case 'dir':
8264
            case TOOL_DOCUMENT:
8265
                $title = $row_item['title'];
8266
                $output = '-';
8267
                if (!empty($title)) {
8268
                    $output = $title;
8269
                }
8270
                break;
8271
            case 'hotpotatoes':
8272
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8273
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
8274
                $myrow = Database::fetch_array($result);
8275
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
8276
                $last = count($pathname) - 1; // Making a correct name for the link.
8277
                $filename = $pathname[$last]; // Making a correct name for the link.
8278
                $myrow['path'] = rawurlencode($myrow['path']);
8279
                $output = $filename;
8280
                break;
8281
        }
8282
8283
        return stripslashes($output);
8284
    }
8285
8286
    /**
8287
     * Get the parent names for the current item.
8288
     *
8289
     * @param int $newItemId Optional. The item ID
8290
     *
8291
     * @return array
8292
     */
8293
    public function getCurrentItemParentNames($newItemId = 0)
8294
    {
8295
        $newItemId = $newItemId ?: $this->get_current_item_id();
8296
        $return = [];
8297
        $item = $this->getItem($newItemId);
8298
        $parent = $this->getItem($item->get_parent());
8299
8300
        while ($parent) {
8301
            $return[] = $parent->get_title();
8302
            $parent = $this->getItem($parent->get_parent());
8303
        }
8304
8305
        return array_reverse($return);
8306
    }
8307
8308
    /**
8309
     * Reads and process "lp_subscription_settings" setting.
8310
     *
8311
     * @return array
8312
     */
8313
    public static function getSubscriptionSettings()
8314
    {
8315
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
8316
        if (empty($subscriptionSettings)) {
8317
            // By default allow both settings
8318
            $subscriptionSettings = [
8319
                'allow_add_users_to_lp' => true,
8320
                'allow_add_users_to_lp_category' => true,
8321
            ];
8322
        } else {
8323
            $subscriptionSettings = $subscriptionSettings['options'];
8324
        }
8325
8326
        return $subscriptionSettings;
8327
    }
8328
8329
    /**
8330
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
8331
     */
8332
    public function exportToCourseBuildFormat()
8333
    {
8334
        if (!api_is_allowed_to_edit()) {
8335
            return false;
8336
        }
8337
8338
        $courseBuilder = new CourseBuilder();
8339
        $itemList = [];
8340
        /** @var learnpathItem $item */
8341
        foreach ($this->items as $item) {
8342
            $itemList[$item->get_type()][] = $item->get_path();
8343
        }
8344
8345
        if (empty($itemList)) {
8346
            return false;
8347
        }
8348
8349
        if (isset($itemList['document'])) {
8350
            // Get parents
8351
            foreach ($itemList['document'] as $documentId) {
8352
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
8353
                if (!empty($documentInfo['parents'])) {
8354
                    foreach ($documentInfo['parents'] as $parentInfo) {
8355
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
8356
                            continue;
8357
                        }
8358
                        $itemList['document'][] = $parentInfo['iid'];
8359
                    }
8360
                }
8361
            }
8362
8363
            $courseInfo = api_get_course_info();
8364
            foreach ($itemList['document'] as $documentId) {
8365
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
8366
                $items = DocumentManager::get_resources_from_source_html(
8367
                    $documentInfo['absolute_path'],
8368
                    true,
8369
                    TOOL_DOCUMENT
8370
                );
8371
8372
                if (!empty($items)) {
8373
                    foreach ($items as $item) {
8374
                        // Get information about source url
8375
                        $url = $item[0]; // url
8376
                        $scope = $item[1]; // scope (local, remote)
8377
                        $type = $item[2]; // type (rel, abs, url)
8378
8379
                        $origParseUrl = parse_url($url);
8380
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
8381
8382
                        if ('local' === $scope) {
8383
                            if ('abs' === $type || 'rel' === $type) {
8384
                                $documentFile = strstr($realOrigPath, 'document');
8385
                                if (false !== strpos($realOrigPath, $documentFile)) {
8386
                                    $documentFile = str_replace('document', '', $documentFile);
8387
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
8388
                                    // Document found! Add it to the list
8389
                                    if ($itemDocumentId) {
8390
                                        $itemList['document'][] = $itemDocumentId;
8391
                                    }
8392
                                }
8393
                            }
8394
                        }
8395
                    }
8396
                }
8397
            }
8398
8399
            $courseBuilder->build_documents(
8400
                api_get_session_id(),
8401
                $this->get_course_int_id(),
8402
                true,
8403
                $itemList['document']
8404
            );
8405
        }
8406
8407
        if (isset($itemList['quiz'])) {
8408
            $courseBuilder->build_quizzes(
8409
                api_get_session_id(),
8410
                $this->get_course_int_id(),
8411
                true,
8412
                $itemList['quiz']
8413
            );
8414
        }
8415
8416
        if (!empty($itemList['thread'])) {
8417
            $threadList = [];
8418
            $repo = Container::getForumThreadRepository();
8419
            foreach ($itemList['thread'] as $threadId) {
8420
                /** @var CForumThread $thread */
8421
                $thread = $repo->find($threadId);
8422
                if ($thread) {
8423
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
8424
                    $threadList[] = $thread->getIid();
8425
                }
8426
            }
8427
8428
            if (!empty($threadList)) {
8429
                $courseBuilder->build_forum_topics(
8430
                    api_get_session_id(),
8431
                    $this->get_course_int_id(),
8432
                    null,
8433
                    $threadList
8434
                );
8435
            }
8436
        }
8437
8438
        $forumCategoryList = [];
8439
        if (isset($itemList['forum'])) {
8440
            foreach ($itemList['forum'] as $forumId) {
8441
                $forumInfo = get_forums($forumId);
8442
                $forumCategoryList[] = $forumInfo['forum_category'];
8443
            }
8444
        }
8445
8446
        if (!empty($forumCategoryList)) {
8447
            $courseBuilder->build_forum_category(
8448
                api_get_session_id(),
8449
                $this->get_course_int_id(),
8450
                true,
8451
                $forumCategoryList
8452
            );
8453
        }
8454
8455
        if (!empty($itemList['forum'])) {
8456
            $courseBuilder->build_forums(
8457
                api_get_session_id(),
8458
                $this->get_course_int_id(),
8459
                true,
8460
                $itemList['forum']
8461
            );
8462
        }
8463
8464
        if (isset($itemList['link'])) {
8465
            $courseBuilder->build_links(
8466
                api_get_session_id(),
8467
                $this->get_course_int_id(),
8468
                true,
8469
                $itemList['link']
8470
            );
8471
        }
8472
8473
        if (!empty($itemList['student_publication'])) {
8474
            $courseBuilder->build_works(
8475
                api_get_session_id(),
8476
                $this->get_course_int_id(),
8477
                true,
8478
                $itemList['student_publication']
8479
            );
8480
        }
8481
8482
        $courseBuilder->build_learnpaths(
8483
            api_get_session_id(),
8484
            $this->get_course_int_id(),
8485
            true,
8486
            [$this->get_id()],
8487
            false
8488
        );
8489
8490
        $courseBuilder->restoreDocumentsFromList();
8491
8492
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
8493
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
8494
        $result = DocumentManager::file_send_for_download(
8495
            $zipPath,
8496
            true,
8497
            $this->get_name().'.zip'
8498
        );
8499
8500
        if ($result) {
8501
            api_not_allowed();
8502
        }
8503
8504
        return true;
8505
    }
8506
8507
    /**
8508
     * Get whether this is a learning path with the accumulated work time or not.
8509
     *
8510
     * @return int
8511
     */
8512
    public function getAccumulateWorkTime()
8513
    {
8514
        return (int) $this->accumulateWorkTime;
8515
    }
8516
8517
    /**
8518
     * Get whether this is a learning path with the accumulated work time or not.
8519
     *
8520
     * @return int
8521
     */
8522
    public function getAccumulateWorkTimeTotalCourse()
8523
    {
8524
        $table = Database::get_course_table(TABLE_LP_MAIN);
8525
        $sql = "SELECT SUM(accumulate_work_time) AS total
8526
                FROM $table
8527
                WHERE c_id = ".$this->course_int_id;
8528
        $result = Database::query($sql);
8529
        $row = Database::fetch_array($result);
8530
8531
        return (int) $row['total'];
8532
    }
8533
8534
    /**
8535
     * @param int $lpId
8536
     * @param int $courseId
8537
     *
8538
     * @return mixed
8539
     */
8540
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
8541
    {
8542
        $lpId = (int) $lpId;
8543
        $table = Database::get_course_table(TABLE_LP_MAIN);
8544
        $sql = "SELECT accumulate_work_time
8545
                FROM $table
8546
                WHERE iid = $lpId";
8547
        $result = Database::query($sql);
8548
        $row = Database::fetch_array($result);
8549
8550
        return $row['accumulate_work_time'];
8551
    }
8552
8553
    /**
8554
     * @param int $courseId
8555
     *
8556
     * @return int
8557
     */
8558
    public static function getAccumulateWorkTimeTotal($courseId)
8559
    {
8560
        $table = Database::get_course_table(TABLE_LP_MAIN);
8561
        $courseId = (int) $courseId;
8562
        $sql = "SELECT SUM(accumulate_work_time) AS total
8563
                FROM $table
8564
                WHERE c_id = $courseId";
8565
        $result = Database::query($sql);
8566
        $row = Database::fetch_array($result);
8567
8568
        return (int) $row['total'];
8569
    }
8570
8571
    /**
8572
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
8573
     * and put the images in.
8574
     *
8575
     * @return array
8576
     */
8577
    public static function getIconSelect()
8578
    {
8579
        $theme = api_get_visual_theme();
8580
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
8581
        $icons = ['' => get_lang('Please select an option')];
8582
8583
        if (is_dir($path)) {
8584
            $finder = new Finder();
8585
            $finder->files()->in($path);
8586
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
8587
            /** @var SplFileInfo $file */
8588
            foreach ($finder as $file) {
8589
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
8590
                    $icons[$file->getFilename()] = $file->getFilename();
8591
                }
8592
            }
8593
        }
8594
8595
        return $icons;
8596
    }
8597
8598
    /**
8599
     * @param int $lpId
8600
     *
8601
     * @return string
8602
     */
8603
    public static function getSelectedIcon($lpId)
8604
    {
8605
        $extraFieldValue = new ExtraFieldValue('lp');
8606
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
8607
        $icon = '';
8608
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
8609
            $icon = $lpIcon['value'];
8610
        }
8611
8612
        return $icon;
8613
    }
8614
8615
    /**
8616
     * @param int $lpId
8617
     *
8618
     * @return string
8619
     */
8620
    public static function getSelectedIconHtml($lpId)
8621
    {
8622
        $icon = self::getSelectedIcon($lpId);
8623
8624
        if (empty($icon)) {
8625
            return '';
8626
        }
8627
8628
        $theme = api_get_visual_theme();
8629
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
8630
8631
        return Display::img($path);
8632
    }
8633
8634
    /**
8635
     * @param string $value
8636
     *
8637
     * @return string
8638
     */
8639
    public function cleanItemTitle($value)
8640
    {
8641
        $value = Security::remove_XSS(strip_tags($value));
8642
8643
        return $value;
8644
    }
8645
8646
    public function setItemTitle(FormValidator $form)
8647
    {
8648
        if (api_get_configuration_value('save_titles_as_html')) {
8649
            $form->addHtmlEditor(
8650
                'title',
8651
                get_lang('Title'),
8652
                true,
8653
                false,
8654
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
8655
            );
8656
        } else {
8657
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
8658
            $form->applyFilter('title', 'trim');
8659
            $form->applyFilter('title', 'html_filter');
8660
        }
8661
    }
8662
8663
    /**
8664
     * @return array
8665
     */
8666
    public function getItemsForForm($addParentCondition = false)
8667
    {
8668
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8669
8670
        $sql = "SELECT * FROM $tbl_lp_item
8671
                WHERE path <> 'root' AND lp_id = ".$this->lp_id;
8672
8673
        if ($addParentCondition) {
8674
            $sql .= ' AND parent_item_id IS NULL ';
8675
        }
8676
        $sql .= ' ORDER BY display_order ASC';
8677
8678
        $result = Database::query($sql);
8679
        $arrLP = [];
8680
        while ($row = Database::fetch_array($result)) {
8681
            $arrLP[] = [
8682
                'iid' => $row['iid'],
8683
                'id' => $row['iid'],
8684
                'item_type' => $row['item_type'],
8685
                'title' => $this->cleanItemTitle($row['title']),
8686
                'title_raw' => $row['title'],
8687
                'path' => $row['path'],
8688
                'description' => Security::remove_XSS($row['description']),
8689
                'parent_item_id' => $row['parent_item_id'],
8690
                'previous_item_id' => $row['previous_item_id'],
8691
                'next_item_id' => $row['next_item_id'],
8692
                'display_order' => $row['display_order'],
8693
                'max_score' => $row['max_score'],
8694
                'min_score' => $row['min_score'],
8695
                'mastery_score' => $row['mastery_score'],
8696
                'prerequisite' => $row['prerequisite'],
8697
                'max_time_allowed' => $row['max_time_allowed'],
8698
                'prerequisite_min_score' => $row['prerequisite_min_score'],
8699
                'prerequisite_max_score' => $row['prerequisite_max_score'],
8700
            ];
8701
        }
8702
8703
        return $arrLP;
8704
    }
8705
8706
    /**
8707
     * Gets whether this SCORM learning path has been marked to use the score
8708
     * as progress. Takes into account whether the learnpath matches (SCORM
8709
     * content + less than 2 items).
8710
     *
8711
     * @return bool True if the score should be used as progress, false otherwise
8712
     */
8713
    public function getUseScoreAsProgress()
8714
    {
8715
        // If not a SCORM, we don't care about the setting
8716
        if (2 != $this->get_type()) {
8717
            return false;
8718
        }
8719
        // If more than one step in the SCORM, we don't care about the setting
8720
        if ($this->get_total_items_count() > 1) {
8721
            return false;
8722
        }
8723
        $extraFieldValue = new ExtraFieldValue('lp');
8724
        $doUseScore = false;
8725
        $useScore = $extraFieldValue->get_values_by_handler_and_field_variable(
8726
            $this->get_id(),
8727
            'use_score_as_progress'
8728
        );
8729
        if (!empty($useScore) && isset($useScore['value'])) {
8730
            $doUseScore = $useScore['value'];
8731
        }
8732
8733
        return $doUseScore;
8734
    }
8735
8736
    /**
8737
     * Get the user identifier (user_id or username
8738
     * Depends on scorm_api_username_as_student_id in app/config/configuration.php.
8739
     *
8740
     * @return string User ID or username, depending on configuration setting
8741
     */
8742
    public static function getUserIdentifierForExternalServices()
8743
    {
8744
        if (api_get_configuration_value('scorm_api_username_as_student_id')) {
8745
            return api_get_user_info(api_get_user_id())['username'];
8746
        } elseif (null != api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id')) {
8747
            $extraFieldValue = new ExtraFieldValue('user');
8748
            $extrafield = $extraFieldValue->get_values_by_handler_and_field_variable(
8749
                api_get_user_id(),
8750
                api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id')
8751
            );
8752
8753
            return $extrafield['value'];
8754
        } else {
8755
            return api_get_user_id();
8756
        }
8757
    }
8758
8759
    /**
8760
     * Save the new order for learning path items.
8761
     *
8762
     * @param array $orderList A associative array with id and parent_id keys.
8763
     */
8764
    public static function sortItemByOrderList(CLpItem $rootItem, array $orderList = [], $flush = true)
8765
    {
8766
        if (empty($orderList)) {
8767
            return true;
8768
        }
8769
        $lpItemRepo = Container::getLpItemRepository();
8770
        $em = Database::getManager();
8771
        $counter = 2;
8772
        $rootItem->setDisplayOrder(1);
8773
        $rootItem->setPreviousItemId(null);
8774
        $em->persist($rootItem);
8775
        if ($flush) {
8776
            $em->flush();
8777
        }
8778
8779
        foreach ($orderList as $item) {
8780
            $itemId = $item->id ?? 0;
8781
            if (empty($itemId)) {
8782
                continue;
8783
            }
8784
            $parentId = $item->parent_id ?? 0;
8785
            $parent = $rootItem;
8786
            if (!empty($parentId)) {
8787
                $parentExists = $lpItemRepo->find($parentId);
8788
                if (null !== $parentExists) {
8789
                    $parent = $parentExists;
8790
                }
8791
            }
8792
8793
            /** @var CLpItem $itemEntity */
8794
            $itemEntity = $lpItemRepo->find($itemId);
8795
            $itemEntity->setParent($parent);
8796
            $previousId = (int) $itemEntity->getPreviousItemId();
8797
            //if (0 === $previousId) {
8798
                $itemEntity->setPreviousItemId(null);
8799
            //}
8800
8801
            $nextId = (int) $itemEntity->getNextItemId();
8802
            //if (0 === $nextId) {
8803
                $itemEntity->setNextItemId(null);
8804
            //}
8805
8806
8807
            $itemEntity->setDisplayOrder($counter);
8808
            $em->persist($itemEntity);
8809
            if ($flush) {
8810
                $em->flush();
8811
            }
8812
            $counter++;
8813
        }
8814
8815
        $em->flush();
8816
        $lpItemRepo->recoverNode($rootItem, 'displayOrder');
8817
        $em->persist($rootItem);
8818
        if ($flush) {
8819
            $em->flush();
8820
        }
8821
8822
        return true;
8823
    }
8824
8825
    /**
8826
     * Get the depth level of LP item.
8827
     *
8828
     * @param array $items
8829
     * @param int   $currentItemId
8830
     *
8831
     * @return int
8832
     */
8833
    private static function get_level_for_item($items, $currentItemId)
8834
    {
8835
        $parentItemId = 0;
8836
        if (isset($items[$currentItemId])) {
8837
            $parentItemId = $items[$currentItemId]->parent;
8838
        }
8839
8840
        if (0 == $parentItemId) {
8841
            return 0;
8842
        }
8843
8844
        return self::get_level_for_item($items, $parentItemId) + 1;
8845
    }
8846
8847
    /**
8848
     * Generate the link for a learnpath category as course tool.
8849
     *
8850
     * @param int $categoryId
8851
     *
8852
     * @return string
8853
     */
8854
    private static function getCategoryLinkForTool($categoryId)
8855
    {
8856
        $categoryId = (int) $categoryId;
8857
        return 'lp/lp_controller.php?'.api_get_cidreq().'&'
8858
            .http_build_query(
8859
                [
8860
                    'action' => 'view_category',
8861
                    'id' => $categoryId,
8862
                ]
8863
            );
8864
    }
8865
8866
    /**
8867
     * Check and obtain the lp final item if exist.
8868
     *
8869
     * @return learnpathItem
8870
     */
8871
    private function getFinalItem()
8872
    {
8873
        if (empty($this->items)) {
8874
            return null;
8875
        }
8876
8877
        foreach ($this->items as $item) {
8878
            if ('final_item' !== $item->type) {
8879
                continue;
8880
            }
8881
8882
            return $item;
8883
        }
8884
    }
8885
8886
    /**
8887
     * Get the LP Final Item Template.
8888
     *
8889
     * @return string
8890
     */
8891
    private function getFinalItemTemplate()
8892
    {
8893
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
8894
    }
8895
8896
    /**
8897
     * Get the LP Final Item Url.
8898
     *
8899
     * @return string
8900
     */
8901
    private function getSavedFinalItem()
8902
    {
8903
        $finalItem = $this->getFinalItem();
8904
8905
        $repo = Container::getDocumentRepository();
8906
        /** @var CDocument $document */
8907
        $document = $repo->find($finalItem->path);
8908
8909
        if ($document && $document->getResourceNode()->hasResourceFile()) {
8910
            return $repo->getResourceFileContent($document);
8911
        }
8912
8913
        return '';
8914
    }
8915
}
8916