Passed
Push — master ( 7dd8b7...258428 )
by Julito
07:42
created

learnpath   F

Complexity

Total Complexity 1051

Size/Duplication

Total Lines 8923
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 4232
dl 0
loc 8923
rs 0.8
c 2
b 0
f 0
wmc 1051

184 Methods

Rating   Name   Duplication   Size   Complexity  
A get_course_int_id() 0 3 1
A set_course_int_id() 0 3 1
F __construct() 0 252 41
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 getDropElementHtml() 0 12 2
D display_edit_item() 0 71 20
A get_total_items_count() 0 3 1
A display_thread_form() 0 13 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 displayDocumentForm() 0 44 4
A getCategoryFromCourseIntoSelect() 0 18 4
F get_link() 0 316 46
A switch_attempt_mode() 0 16 4
A getAccumulateScormTime() 0 3 1
C create_document() 0 104 11
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 createForum() 0 16 1
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
A displayItemMenu() 0 89 5
A getUserIdentifierForExternalServices() 0 14 3
B set_current_item() 0 33 10
A getAccumulateWorkTime() 0 3 1
A display_lp_prerequisites_list() 0 32 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 display_link_form() 0 22 2
A getForum() 0 9 1
A get_progress_bar() 0 9 1
B getFinalItemForm() 0 84 4
A getExercisesItems() 0 13 3
A getSelectedIcon() 0 10 3
C getBuildTree() 0 195 11
B sortItemByOrderList() 0 61 9
A getCountCategories() 0 10 2
F createReadOutText() 0 121 27
A get_type_static() 0 16 3
A moveDownCategory() 0 11 2
A getAccumulateWorkTimePrerequisite() 0 11 1
A getSelectedIconHtml() 0 12 2
A get_theme() 0 7 2
A get_items_status_list() 0 10 2
A select_previous_item_id() 0 22 1
A display_item_form() 0 14 1
A get_previous_index() 0 16 5
B add_item() 0 117 8
A set_seriousgame_mode() 0 15 3
A getItemsForForm() 0 38 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 getCurrentBuildingModeURL() 0 11 5
A display_document() 0 25 3
A save_current() 0 32 6
A getLastInFirstLevel() 0 13 2
A getChapterTypes() 0 4 1
A displayNewSectionForm() 0 19 1
C rl_get_resource_name() 0 92 14
A getSavedFinalItem() 0 13 3
B restart() 0 38 6
A cleanItemTitle() 0 5 1
B get_iv_interactions_array() 0 54 8
A getCurrentItemParentNames() 0 13 3
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
A generate_lp_folder() 0 35 4
A getAccumulateWorkTimeTotalCourse() 0 10 1
A display_quiz_form() 0 12 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
D display_item_prerequisites_form() 0 210 15
B get_scorm_prereq_string() 0 73 11
B isFirstOrLastItem() 0 32 6
C getCalculateStars() 0 79 12
C display_move_item() 0 55 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
B get_js_dropdown_array() 0 76 6
A getFinalEvaluationItem() 0 12 3
A setItemTitle() 0 14 2
F build_action_menu() 0 190 15
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
A getCategoryLinkForTool() 0 8 1
A getSubscriptionSettings() 0 14 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 edit_document() 0 12 4
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
A getUseScoreAsProgress() 0 21 5
C add_lp() 0 107 11
A getFinalItem() 0 12 4
A displayResources() 0 52 2
A getFinalItemTemplate() 0 3 1
A update_reinit() 0 16 3
A getListArrayToc() 0 9 1
A get_view_id() 0 7 2
F rl_get_resource_link_for_learnpath() 0 160 31
A get_extension() 0 5 1
A getAccumulateWorkTimeTotal() 0 11 1
A getLpFromSession() 0 33 5
A toggleCategoryPublish() 0 25 4
A generate_learning_path_folder() 0 16 2
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 getIconSelect() 0 19 4
F exportToCourseBuildFormat() 0 197 31
A get_previous_item_id() 0 5 1
A get_toc() 0 19 2
A get_level_for_item() 0 12 3
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 display_student_publication_form() 0 12 1
A getStatusCSSClassName() 0 7 2
A delete_children_items() 0 22 4
B save_item() 0 47 9

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 = (int) $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).'/';
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 = $this->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
                    //echo '<pre>';                    var_dump($this->items);                    echo '</pre>';
313
                    foreach ($lp_item_id_list as $item_id) {
314
                        if (isset($status_list[$item_id])) {
315
                            $status = $status_list[$item_id];
316
                            if (is_object($this->items[$item_id])) {
317
                                $this->items[$item_id]->set_status($status);
318
                                if (empty($status)) {
319
                                    $this->items[$item_id]->set_status(
320
                                        $this->default_status
321
                                    );
322
                                }
323
                            }
324
                        } else {
325
                            if (!api_is_invitee()) {
326
                                if (isset($this->items[$item_id]) && is_object($this->items[$item_id])) {
327
                                    $this->items[$item_id]->set_status(
328
                                        $this->default_status
329
                                    );
330
                                }
331
332
                                if (!empty($this->lp_view_id)) {
333
                                    // Add that row to the lp_item_view table so that
334
                                    // we have something to show in the stats page.
335
                                    $params = [
336
                                        'lp_item_id' => $item_id,
337
                                        'lp_view_id' => $this->lp_view_id,
338
                                        'view_count' => 1,
339
                                        'status' => 'not attempted',
340
                                        'start_time' => time(),
341
                                        'total_time' => 0,
342
                                        'score' => 0,
343
                                    ];
344
                                    Database::insert($itemViewTable, $params);
345
346
                                    $this->items[$item_id]->set_lp_view(
347
                                        $this->lp_view_id,
348
                                        $course_id
349
                                    );
350
                                }
351
                            }
352
                        }
353
                    }
354
                }
355
            }
356
357
            $this->ordered_items = self::get_flat_ordered_items_list($this->entity, null);
358
            $this->max_ordered_items = 0;
359
            foreach ($this->ordered_items as $index => $dummy) {
360
                if ($index > $this->max_ordered_items && !empty($dummy)) {
361
                    $this->max_ordered_items = $index;
362
                }
363
            }
364
            // TODO: Define the current item better.
365
            $this->first();
366
            if ($debug) {
367
                error_log('lp_view_session_id '.$this->lp_view_session_id);
368
                error_log('End of learnpath constructor for learnpath '.$this->get_id());
369
            }
370
        }
371
    }
372
373
    /**
374
     * @return int
375
     */
376
    public function get_course_int_id()
377
    {
378
        return $this->course_int_id ?? api_get_course_int_id();
379
    }
380
381
    /**
382
     * @param $course_id
383
     *
384
     * @return int
385
     */
386
    public function set_course_int_id($course_id)
387
    {
388
        return $this->course_int_id = (int) $course_id;
389
    }
390
391
    /**
392
     * Function rewritten based on old_add_item() from Yannick Warnier.
393
     * Due the fact that users can decide where the item should come, I had to overlook this function and
394
     * I found it better to rewrite it. Old function is still available.
395
     * Added also the possibility to add a description.
396
     *
397
     * @param CLpItem $parent
398
     * @param int     $previousId
399
     * @param string  $type
400
     * @param int     $id resource ID (ref)
401
     * @param string  $title
402
     * @param string  $description
403
     * @param int     $prerequisites
404
     * @param int     $max_time_allowed
405
     * @param int     $userId
406
     *
407
     * @return int
408
     */
409
    public function add_item(
410
        ?CLpItem $parent,
411
        $previousId,
412
        $type,
413
        $id,
414
        $title,
415
        $description = '',
416
        $prerequisites = 0,
417
        $max_time_allowed = 0
418
    ) {
419
        $type = empty($type) ? 'dir' : $type;
420
        $course_id = $this->course_info['real_id'];
421
        if (empty($course_id)) {
422
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
423
            $this->course_info = api_get_course_info($this->cc);
424
            $course_id = $this->course_info['real_id'];
425
        }
426
        $id = (int) $id;
427
        $max_time_allowed = (int) $max_time_allowed;
428
        if (empty($max_time_allowed)) {
429
            $max_time_allowed = 0;
430
        }
431
        $max_score = 100;
432
        if ('quiz' === $type && $id) {
433
            /*$sql = 'SELECT SUM(ponderation)
434
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as q
435
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
436
                    ON q.iid = quiz_rel_question.question_id
437
                    WHERE
438
                        quiz_rel_question.quiz_id = '.$id;
439
            $rsQuiz = Database::query($sql);
440
            $max_score = Database::result($rsQuiz, 0, 0);*/
441
442
            // Disabling the exercise if we add it inside a LP
443
            $exercise = new Exercise($course_id);
444
            $exercise->read($id);
445
            $max_score = $exercise->get_max_score();
446
447
            $exercise->disable();
448
            $exercise->save();
449
            $title = $exercise->get_formated_title();
450
        }
451
452
        $lpItem = (new CLpItem())
453
            ->setTitle($title)
454
            ->setDescription($description)
455
            ->setPath($id)
456
            ->setLp($this->entity)
457
            ->setItemType($type)
458
            ->setMaxScore($max_score)
459
            ->setMaxTimeAllowed($max_time_allowed)
460
            ->setPrerequisite($prerequisites)
461
            //->setDisplayOrder($display_order + 1)
462
            //->setNextItemId((int) $next)
463
            //->setPreviousItemId($previous)
464
        ;
465
466
        if (!empty($parent))  {
467
            $lpItem->setParent($parent);
468
        }
469
        $em = Database::getManager();
470
        $em->persist($lpItem);
471
        $em->flush();
472
473
        $new_item_id = $lpItem->getIid();
474
        if ($new_item_id) {
475
            // @todo fix upload audio.
476
            // Upload audio.
477
            /*if (!empty($_FILES['mp3']['name'])) {
478
                // Create the audio folder if it does not exist yet.
479
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
480
                if (!is_dir($filepath.'audio')) {
481
                    mkdir(
482
                        $filepath.'audio',
483
                        api_get_permissions_for_new_directories()
484
                    );
485
                    DocumentManager::addDocument(
486
                        $_course,
487
                        '/audio',
488
                        'folder',
489
                        0,
490
                        'audio',
491
                        '',
492
                        0,
493
                        true,
494
                        null,
495
                        $sessionId,
496
                        $userId
497
                    );
498
                }
499
500
                $file_path = handle_uploaded_document(
501
                    $_course,
502
                    $_FILES['mp3'],
503
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
504
                    '/audio',
505
                    $userId,
506
                    '',
507
                    '',
508
                    '',
509
                    '',
510
                    false
511
                );
512
513
                // Getting the filename only.
514
                $file_components = explode('/', $file_path);
515
                $file = $file_components[count($file_components) - 1];
516
517
                // Store the mp3 file in the lp_item table.
518
                $sql = "UPDATE $tbl_lp_item SET
519
                          audio = '".Database::escape_string($file)."'
520
                        WHERE iid = '".intval($new_item_id)."'";
521
                Database::query($sql);
522
            }*/
523
        }
524
525
        return $new_item_id;
526
    }
527
528
    /**
529
     * Static admin function allowing addition of a learnpath to a course.
530
     *
531
     * @param string $courseCode
532
     * @param string $name
533
     * @param string $description
534
     * @param string $learnpath
535
     * @param string $origin
536
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
537
     * @param string $publicated_on
538
     * @param string $expired_on
539
     * @param int    $categoryId
540
     * @param int    $userId
541
     *
542
     * @return CLp
543
     */
544
    public static function add_lp(
545
        $courseCode,
546
        $name,
547
        $description = '',
548
        $learnpath = 'guess',
549
        $origin = 'zip',
550
        $zipname = '',
551
        $publicated_on = '',
552
        $expired_on = '',
553
        $categoryId = 0,
554
        $userId = 0
555
    ) {
556
        global $charset;
557
558
        if (!empty($courseCode)) {
559
            $courseInfo = api_get_course_info($courseCode);
560
            $course_id = $courseInfo['real_id'];
561
        } else {
562
            $course_id = api_get_course_int_id();
563
            $courseInfo = api_get_course_info();
564
        }
565
566
        $categoryId = (int) $categoryId;
567
568
        if (empty($publicated_on)) {
569
            $publicated_on = null;
570
        } else {
571
            $publicated_on = api_get_utc_datetime($publicated_on, true, true);
572
        }
573
574
        if (empty($expired_on)) {
575
            $expired_on = null;
576
        } else {
577
            $expired_on = api_get_utc_datetime($expired_on, true, true);
578
        }
579
580
        /*$check_name = "SELECT * FROM $tbl_lp
581
                       WHERE c_id = $course_id AND name = '".Database::escape_string($name)."'";
582
        $res_name = Database::query($check_name);
583
584
        while (Database::num_rows($res_name)) {
585
            // There is already one such name, update the current one a bit.
586
            $i++;
587
            $name = $name.' - '.$i;
588
            $check_name = "SELECT * FROM $tbl_lp
589
                           WHERE c_id = $course_id AND name = '".Database::escape_string($name)."' ";
590
            $res_name = Database::query($check_name);
591
        }*/
592
        // New name does not exist yet; keep it.
593
        // Escape description.
594
        // Kevin: added htmlentities().
595
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
596
        $type = 1;
597
        switch ($learnpath) {
598
            case 'guess':
599
            case 'aicc':
600
                break;
601
            case 'dokeos':
602
            case 'chamilo':
603
                $type = 1;
604
                break;
605
        }
606
607
        $sessionEntity = api_get_session_entity();
608
        $courseEntity = api_get_course_entity($courseInfo['real_id']);
609
        $lp = null;
610
        switch ($origin) {
611
            case 'zip':
612
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
613
                break;
614
            case 'manual':
615
            default:
616
                /*$get_max = "SELECT MAX(display_order)
617
                            FROM $tbl_lp WHERE c_id = $course_id";
618
                $res_max = Database::query($get_max);
619
                if (Database::num_rows($res_max) < 1) {
620
                    $dsp = 1;
621
                } else {
622
                    $row = Database::fetch_array($res_max);
623
                    $dsp = $row[0] + 1;
624
                }*/
625
626
                $dsp = 1;
627
                $category = null;
628
                if (!empty($categoryId)) {
629
                    $category = Container::getLpCategoryRepository()->find($categoryId);
630
                }
631
632
                $lpRepo = Container::getLpRepository();
633
634
                $lp = (new CLp())
635
                    ->setLpType($type)
636
                    ->setName($name)
637
                    ->setDescription($description)
638
                    ->setDisplayOrder($dsp)
639
                    ->setCategory($category)
640
                    ->setPublicatedOn($publicated_on)
641
                    ->setExpiredOn($expired_on)
642
                    ->setParent($courseEntity)
643
                    ->addCourseLink($courseEntity, $sessionEntity)
644
                ;
645
                $lpRepo->createLp($lp);
646
647
                break;
648
        }
649
650
        return $lp;
651
    }
652
653
    /**
654
     * Auto completes the parents of an item in case it's been completed or passed.
655
     *
656
     * @param int $item Optional ID of the item from which to look for parents
657
     */
658
    public function autocomplete_parents($item)
659
    {
660
        $debug = $this->debug;
661
662
        if (empty($item)) {
663
            $item = $this->current;
664
        }
665
666
        $currentItem = $this->getItem($item);
667
        if ($currentItem) {
668
            $parent_id = $currentItem->get_parent();
669
            $parent = $this->getItem($parent_id);
670
            if ($parent) {
671
                // if $item points to an object and there is a parent.
672
                if ($debug) {
673
                    error_log(
674
                        'Autocompleting parent of item '.$item.' '.
675
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
676
                        0
677
                    );
678
                }
679
680
                // New experiment including failed and browsed in completed status.
681
                //$current_status = $currentItem->get_status();
682
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
683
                // Fixes chapter auto complete
684
                if (true) {
685
                    // If the current item is completed or passes or succeeded.
686
                    $updateParentStatus = true;
687
                    if ($debug) {
688
                        error_log('Status of current item is alright');
689
                    }
690
691
                    foreach ($parent->get_children() as $childItemId) {
692
                        $childItem = $this->getItem($childItemId);
693
694
                        // If children was not set try to get the info
695
                        if (empty($childItem->db_item_view_id)) {
696
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
697
                        }
698
699
                        // Check all his brothers (parent's children) for completion status.
700
                        if ($childItemId != $item) {
701
                            if ($debug) {
702
                                error_log(
703
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
704
                                    0
705
                                );
706
                            }
707
                            // Trying completing parents of failed and browsed items as well.
708
                            if ($childItem->status_is(
709
                                [
710
                                    'completed',
711
                                    'passed',
712
                                    'succeeded',
713
                                    'browsed',
714
                                    'failed',
715
                                ]
716
                            )
717
                            ) {
718
                                // Keep completion status to true.
719
                                continue;
720
                            } else {
721
                                if ($debug > 2) {
722
                                    error_log(
723
                                        '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,
724
                                        0
725
                                    );
726
                                }
727
                                $updateParentStatus = false;
728
                                break;
729
                            }
730
                        }
731
                    }
732
733
                    if ($updateParentStatus) {
734
                        // If all the children were completed:
735
                        $parent->set_status('completed');
736
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
737
                        // Force the status to "completed"
738
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
739
                        $this->update_queue[$parent->get_id()] = 'completed';
740
                        if ($debug) {
741
                            error_log(
742
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
743
                                print_r($this->update_queue, 1),
744
                                0
745
                            );
746
                        }
747
                        // Recursive call.
748
                        $this->autocomplete_parents($parent->get_id());
749
                    }
750
                }
751
            } else {
752
                if ($debug) {
753
                    error_log("Parent #$parent_id does not exists");
754
                }
755
            }
756
        } else {
757
            if ($debug) {
758
                error_log("#$item is an item that doesn't have parents");
759
            }
760
        }
761
    }
762
763
    /**
764
     * Closes the current resource.
765
     *
766
     * Stops the timer
767
     * Saves into the database if required
768
     * Clears the current resource data from this object
769
     *
770
     * @return bool True on success, false on failure
771
     */
772
    public function close()
773
    {
774
        if (empty($this->lp_id)) {
775
            $this->error = 'Trying to close this learnpath but no ID is set';
776
777
            return false;
778
        }
779
        $this->current_time_stop = time();
780
        $this->ordered_items = [];
781
        $this->index = 0;
782
        unset($this->lp_id);
783
        //unset other stuff
784
        return true;
785
    }
786
787
    /**
788
     * Static admin function allowing removal of a learnpath.
789
     *
790
     * @param array  $courseInfo
791
     * @param int    $id         Learnpath ID
792
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
793
     *
794
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
795
     */
796
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
797
    {
798
        $course_id = api_get_course_int_id();
799
        if (!empty($courseInfo)) {
800
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
801
        }
802
803
        // TODO: Implement a way of getting this to work when the current object is not set.
804
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
805
        // If an ID is specifically given and the current LP is not the same, prevent delete.
806
        if (!empty($id) && ($id != $this->lp_id)) {
807
            return false;
808
        }
809
810
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
811
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
812
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
813
814
        // Delete lp item id.
815
        foreach ($this->items as $lpItemId => $dummy) {
816
            $sql = "DELETE FROM $lp_item_view
817
                    WHERE lp_item_id = '".$lpItemId."'";
818
            Database::query($sql);
819
        }
820
821
        // Proposed by Christophe (nickname: clefevre)
822
        $sql = "DELETE FROM $lp_item
823
                WHERE lp_id = ".$this->lp_id;
824
        Database::query($sql);
825
826
        $sql = "DELETE FROM $lp_view
827
                WHERE lp_id = ".$this->lp_id;
828
        Database::query($sql);
829
830
        //self::toggleVisibility($this->lp_id, 0);
831
832
        /*if (2 == $this->type || 3 == $this->type) {
833
            // This is a scorm learning path, delete the files as well.
834
            $sql = "SELECT path FROM $lp
835
                    WHERE iid = ".$this->lp_id;
836
            $res = Database::query($sql);
837
            if (Database::num_rows($res) > 0) {
838
                $row = Database::fetch_array($res);
839
                $path = $row['path'];
840
                $sql = "SELECT iid FROM $lp
841
                        WHERE
842
                            c_id = $course_id AND
843
                            path = '$path' AND
844
                            iid != ".$this->lp_id;
845
                $res = Database::query($sql);
846
                if (Database::num_rows($res) > 0) {
847
                    // Another learning path uses this directory, so don't delete it.
848
                    if ($this->debug > 2) {
849
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
850
                    }
851
                } else {
852
                    // No other LP uses that directory, delete it.
853
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
854
                    // The absolute system path for this course.
855
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
856
                    if ('remove' == $delete && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
857
                        if ($this->debug > 2) {
858
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
859
                        }
860
                        // Proposed by Christophe (clefevre).
861
                        if (0 == strcmp(substr($path, -2), "/.")) {
862
                            $path = substr($path, 0, -1); // Remove "." at the end.
863
                        }
864
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
865
                        rmdirr($course_scorm_dir.$path);
866
                    }
867
                }
868
            }
869
        }*/
870
871
        $table = Database::get_course_table(TABLE_LP_REL_USERGROUP);
872
        $sql = "DELETE FROM $table
873
                WHERE
874
                    lp_id = {$this->lp_id}";
875
        Database::query($sql);
876
877
        $repo = Container::getLpRepository();
878
        $lp = $repo->find($this->lp_id);
879
        Database::getManager()->remove($lp);
880
        Database::getManager()->flush();
881
882
        // Updates the display order of all lps.
883
        $this->update_display_order();
884
885
        $link_info = GradebookUtils::isResourceInCourseGradebook(
886
            api_get_course_id(),
887
            4,
888
            $id,
889
            api_get_session_id()
890
        );
891
892
        if (false !== $link_info) {
893
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
894
        }
895
896
        if ('true' === api_get_setting('search_enabled')) {
897
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
898
        }
899
    }
900
901
    /**
902
     * Removes all the children of one item - dangerous!
903
     *
904
     * @param int $id Element ID of which children have to be removed
905
     *
906
     * @return int Total number of children removed
907
     */
908
    public function delete_children_items($id)
909
    {
910
        $course_id = $this->course_info['real_id'];
911
912
        $num = 0;
913
        $id = (int) $id;
914
        if (empty($id) || empty($course_id)) {
915
            return false;
916
        }
917
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
918
        $sql = "SELECT * FROM $lp_item
919
                WHERE parent_item_id = $id";
920
        $res = Database::query($sql);
921
        while ($row = Database::fetch_array($res)) {
922
            $num += $this->delete_children_items($row['iid']);
923
            $sql = "DELETE FROM $lp_item
924
                    WHERE iid = ".$row['iid'];
925
            Database::query($sql);
926
            $num++;
927
        }
928
929
        return $num;
930
    }
931
932
    /**
933
     * Removes an item from the current learnpath.
934
     *
935
     * @param int $id Elem ID (0 if first)
936
     *
937
     * @return int Number of elements moved
938
     *
939
     * @todo implement resource removal
940
     */
941
    public function delete_item($id)
942
    {
943
        $course_id = api_get_course_int_id();
944
        $id = (int) $id;
945
        // TODO: Implement the resource removal.
946
        if (empty($id) || empty($course_id)) {
947
            return false;
948
        }
949
950
        $repo = Container::getLpItemRepository();
951
        $item = $repo->find($id);
952
        if (null === $item) {
953
            return false;
954
        }
955
956
        $em = Database::getManager();
957
        $repo->removeFromTree($item);
958
        $em->flush();
959
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
960
961
        //Removing prerequisites since the item will not longer exist
962
        $sql_all = "UPDATE $lp_item SET prerequisite = ''
963
                    WHERE prerequisite = '$id'";
964
        Database::query($sql_all);
965
966
        $sql = "UPDATE $lp_item
967
                SET previous_item_id = ".$this->getLastInFirstLevel()."
968
                WHERE lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
969
        Database::query($sql);
970
971
        // Remove from search engine if enabled.
972
        if ('true' === api_get_setting('search_enabled')) {
973
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
974
            $sql = 'SELECT * FROM %s
975
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
976
                    LIMIT 1';
977
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
978
            $res = Database::query($sql);
979
            if (Database::num_rows($res) > 0) {
980
                $row2 = Database::fetch_array($res);
981
                $di = new ChamiloIndexer();
982
                $di->remove_document($row2['search_did']);
983
            }
984
            $sql = 'DELETE FROM %s
985
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
986
                    LIMIT 1';
987
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
988
            Database::query($sql);
989
        }
990
    }
991
992
    /**
993
     * Updates an item's content in place.
994
     *
995
     * @param int    $id               Element ID
996
     * @param int    $parent           Parent item ID
997
     * @param int    $previous         Previous item ID
998
     * @param string $title            Item title
999
     * @param string $description      Item description
1000
     * @param string $prerequisites    Prerequisites (optional)
1001
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1002
     * @param int    $max_time_allowed
1003
     * @param string $url
1004
     *
1005
     * @return bool True on success, false on error
1006
     */
1007
    public function edit_item(
1008
        $id,
1009
        $parent,
1010
        $previous,
1011
        $title,
1012
        $description,
1013
        $prerequisites = '0',
1014
        $audio = [],
1015
        $max_time_allowed = 0,
1016
        $url = ''
1017
    ) {
1018
        $_course = api_get_course_info();
1019
        $id = (int) $id;
1020
1021
        if (empty($id) || empty($_course)) {
1022
            return false;
1023
        }
1024
        $repo = Container::getLpItemRepository();
1025
        /** @var CLpItem $item */
1026
        $item = $repo->find($id);
1027
        if (null === $item) {
1028
            return false;
1029
        }
1030
1031
        $item
1032
            ->setTitle($title)
1033
            ->setDescription($description)
1034
            ->setPrerequisite($prerequisites)
1035
            ->setMaxTimeAllowed((int) $max_time_allowed)
1036
        ;
1037
1038
        $em = Database::getManager();
1039
        if (!empty($parent)) {
1040
            $parent = $repo->find($parent);
1041
            $item->setParent($parent);
1042
        } else {
1043
            $item->setParent(null);
1044
        }
1045
1046
        if (!empty($previous)) {
1047
            $previous = $repo->find($previous);
1048
            $repo->persistAsNextSiblingOf( $item, $previous);
1049
        } else {
1050
            $em->persist($item);
1051
        }
1052
1053
        $em->flush();
1054
1055
        if ('link' === $item->getItemType()) {
1056
            $link = new Link();
1057
            $linkId = $item->getPath();
1058
            $link->updateLink($linkId, $url);
1059
        }
1060
    }
1061
1062
    /**
1063
     * Updates an item's prereq in place.
1064
     *
1065
     * @param int    $id              Element ID
1066
     * @param string $prerequisite_id Prerequisite Element ID
1067
     * @param int    $minScore        Prerequisite min score
1068
     * @param int    $maxScore        Prerequisite max score
1069
     *
1070
     * @return bool True on success, false on error
1071
     */
1072
    public function edit_item_prereq($id, $prerequisite_id, $minScore = 0, $maxScore = 100)
1073
    {
1074
        $id = (int) $id;
1075
1076
        if (empty($id)) {
1077
            return false;
1078
        }
1079
        $prerequisite_id = (int) $prerequisite_id;
1080
1081
        if (empty($minScore) || $minScore < 0) {
1082
            $minScore = 0;
1083
        }
1084
1085
        if (empty($maxScore) || $maxScore < 0) {
1086
            $maxScore = 100;
1087
        }
1088
1089
        $minScore = (float) $minScore;
1090
        $maxScore = (float) $maxScore;
1091
1092
        if (empty($prerequisite_id)) {
1093
            $prerequisite_id = 'NULL';
1094
            $minScore = 0;
1095
            $maxScore = 100;
1096
        }
1097
1098
        $table = Database::get_course_table(TABLE_LP_ITEM);
1099
        $sql = " UPDATE $table
1100
                 SET
1101
                    prerequisite = $prerequisite_id ,
1102
                    prerequisite_min_score = $minScore ,
1103
                    prerequisite_max_score = $maxScore
1104
                 WHERE iid = $id";
1105
        Database::query($sql);
1106
1107
        return true;
1108
    }
1109
1110
    /**
1111
     * Get the specific prefix index terms of this learning path.
1112
     *
1113
     * @param string $prefix
1114
     *
1115
     * @return array Array of terms
1116
     */
1117
    public function get_common_index_terms_by_prefix($prefix)
1118
    {
1119
        $terms = get_specific_field_values_list_by_prefix(
1120
            $prefix,
1121
            $this->cc,
1122
            TOOL_LEARNPATH,
1123
            $this->lp_id
1124
        );
1125
        $prefix_terms = [];
1126
        if (!empty($terms)) {
1127
            foreach ($terms as $term) {
1128
                $prefix_terms[] = $term['value'];
1129
            }
1130
        }
1131
1132
        return $prefix_terms;
1133
    }
1134
1135
    /**
1136
     * Gets the number of items currently completed.
1137
     *
1138
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1139
     *
1140
     * @return int The number of items currently completed
1141
     */
1142
    public function get_complete_items_count($failedStatusException = false)
1143
    {
1144
        $i = 0;
1145
        $completedStatusList = [
1146
            'completed',
1147
            'passed',
1148
            'succeeded',
1149
            'browsed',
1150
        ];
1151
1152
        if (!$failedStatusException) {
1153
            $completedStatusList[] = 'failed';
1154
        }
1155
1156
        foreach ($this->items as $id => $dummy) {
1157
            // Trying failed and browsed considered "progressed" as well.
1158
            if ($this->items[$id]->status_is($completedStatusList) &&
1159
                'dir' !== $this->items[$id]->get_type()
1160
            ) {
1161
                $i++;
1162
            }
1163
        }
1164
1165
        return $i;
1166
    }
1167
1168
    /**
1169
     * Gets the current item ID.
1170
     *
1171
     * @return int The current learnpath item id
1172
     */
1173
    public function get_current_item_id()
1174
    {
1175
        $current = 0;
1176
        if (!empty($this->current)) {
1177
            $current = (int) $this->current;
1178
        }
1179
1180
        return $current;
1181
    }
1182
1183
    /**
1184
     * Force to get the first learnpath item id.
1185
     *
1186
     * @return int The current learnpath item id
1187
     */
1188
    public function get_first_item_id()
1189
    {
1190
        $current = 0;
1191
        if (is_array($this->ordered_items)) {
1192
            $current = $this->ordered_items[0];
1193
        }
1194
1195
        return $current;
1196
    }
1197
1198
    /**
1199
     * Gets the total number of items available for viewing in this SCORM.
1200
     *
1201
     * @return int The total number of items
1202
     */
1203
    public function get_total_items_count()
1204
    {
1205
        return count($this->items);
1206
    }
1207
1208
    /**
1209
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1210
     *
1211
     * @return int The total no-chapters number of items
1212
     */
1213
    public function getTotalItemsCountWithoutDirs()
1214
    {
1215
        $total = 0;
1216
        $typeListNotToCount = self::getChapterTypes();
1217
        foreach ($this->items as $temp2) {
1218
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1219
                $total++;
1220
            }
1221
        }
1222
1223
        return $total;
1224
    }
1225
1226
    /**
1227
     *  Sets the first element URL.
1228
     */
1229
    public function first()
1230
    {
1231
        if ($this->debug > 0) {
1232
            error_log('In learnpath::first()', 0);
1233
            error_log('$this->last_item_seen '.$this->last_item_seen);
1234
        }
1235
1236
        // Test if the last_item_seen exists and is not a dir.
1237
        if (0 == count($this->ordered_items)) {
1238
            $this->index = 0;
1239
        }
1240
1241
        if (!empty($this->last_item_seen) &&
1242
            !empty($this->items[$this->last_item_seen]) &&
1243
            'dir' !== $this->items[$this->last_item_seen]->get_type()
1244
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1245
            //&& !$this->items[$this->last_item_seen]->is_done()
1246
        ) {
1247
            if ($this->debug > 2) {
1248
                error_log(
1249
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1250
                    $this->items[$this->last_item_seen]->get_type()
1251
                );
1252
            }
1253
            $index = -1;
1254
            foreach ($this->ordered_items as $myindex => $item_id) {
1255
                if ($item_id == $this->last_item_seen) {
1256
                    $index = $myindex;
1257
                    break;
1258
                }
1259
            }
1260
            if (-1 == $index) {
1261
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1262
                if ($this->debug > 2) {
1263
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1264
                }
1265
1266
                return false;
1267
            } else {
1268
                $this->last = $this->last_item_seen;
1269
                $this->current = $this->last_item_seen;
1270
                $this->index = $index;
1271
            }
1272
        } else {
1273
            if ($this->debug > 2) {
1274
                error_log('In learnpath::first() - No last item seen', 0);
1275
            }
1276
            $index = 0;
1277
            // Loop through all ordered items and stop at the first item that is
1278
            // not a directory *and* that has not been completed yet.
1279
            while (!empty($this->ordered_items[$index]) &&
1280
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1281
                (
1282
                    'dir' === $this->items[$this->ordered_items[$index]]->get_type() ||
1283
                    true === $this->items[$this->ordered_items[$index]]->is_done()
1284
                ) && $index < $this->max_ordered_items
1285
            ) {
1286
                $index++;
1287
            }
1288
1289
            $this->last = $this->current;
1290
            // current is
1291
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1292
            $this->index = $index;
1293
            if ($this->debug > 2) {
1294
                error_log('$index '.$index);
1295
                error_log('In learnpath::first() - No last item seen');
1296
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1297
            }
1298
        }
1299
        if ($this->debug > 2) {
1300
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1301
        }
1302
    }
1303
1304
    /**
1305
     * Gets the js library from the database.
1306
     *
1307
     * @return string The name of the javascript library to be used
1308
     */
1309
    public function get_js_lib()
1310
    {
1311
        $lib = '';
1312
        if (!empty($this->js_lib)) {
1313
            $lib = $this->js_lib;
1314
        }
1315
1316
        return $lib;
1317
    }
1318
1319
    /**
1320
     * Gets the learnpath database ID.
1321
     *
1322
     * @return int Learnpath ID in the lp table
1323
     */
1324
    public function get_id()
1325
    {
1326
        if (!empty($this->lp_id)) {
1327
            return (int) $this->lp_id;
1328
        }
1329
1330
        return 0;
1331
    }
1332
1333
    /**
1334
     * Gets the last element URL.
1335
     *
1336
     * @return string URL to load into the viewer
1337
     */
1338
    public function get_last()
1339
    {
1340
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1341
        if (count($this->ordered_items) > 0) {
1342
            $this->index = count($this->ordered_items) - 1;
1343
1344
            return $this->ordered_items[$this->index];
1345
        }
1346
1347
        return false;
1348
    }
1349
1350
    /**
1351
     * Get the last element in the first level.
1352
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1353
     *
1354
     * @return mixed
1355
     */
1356
    public function getLastInFirstLevel()
1357
    {
1358
        try {
1359
            $lastId = Database::getManager()
1360
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1361
                WHERE i.lp = :lp AND i.parent IS NULL AND i.itemType != :type ORDER BY i.displayOrder DESC')
1362
                ->setMaxResults(1)
1363
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1364
                ->getSingleScalarResult();
1365
1366
            return $lastId;
1367
        } catch (Exception $exception) {
1368
            return 0;
1369
        }
1370
    }
1371
1372
    /**
1373
     * Gets the navigation bar for the learnpath display screen.
1374
     *
1375
     * @param string $barId
1376
     *
1377
     * @return string The HTML string to use as a navigation bar
1378
     */
1379
    public function get_navigation_bar($barId = '')
1380
    {
1381
        if (empty($barId)) {
1382
            $barId = 'control-top';
1383
        }
1384
        $lpId = $this->lp_id;
1385
        $mycurrentitemid = $this->get_current_item_id();
1386
        $reportingText = get_lang('Reporting');
1387
        $previousText = get_lang('Previous');
1388
        $nextText = get_lang('Next');
1389
        $fullScreenText = get_lang('Back to normal screen');
1390
1391
        $settings = api_get_configuration_value('lp_view_settings');
1392
        $display = $settings['display'] ?? false;
1393
        $reportingIcon = '
1394
            <a class="icon-toolbar"
1395
                id="stats_link"
1396
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
1397
                onclick="window.parent.API.save_asset(); return true;"
1398
                target="content_name" title="'.$reportingText.'">
1399
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1400
            </a>';
1401
1402
        if (!empty($display)) {
1403
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1404
            if (false === $showReporting) {
1405
                $reportingIcon = '';
1406
            }
1407
        }
1408
1409
        $hideArrows = false;
1410
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1411
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1412
        }
1413
1414
        $previousIcon = '';
1415
        $nextIcon = '';
1416
        if (false === $hideArrows) {
1417
            $previousIcon = '
1418
                <a class="icon-toolbar" id="scorm-previous" href="#"
1419
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1420
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1421
                </a>';
1422
1423
            $nextIcon = '
1424
                <a class="icon-toolbar" id="scorm-next" href="#"
1425
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1426
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1427
                </a>';
1428
        }
1429
1430
        if ('fullscreen' === $this->mode) {
1431
            $navbar = '
1432
                  <span id="'.$barId.'" class="buttons">
1433
                    '.$reportingIcon.'
1434
                    '.$previousIcon.'
1435
                    '.$nextIcon.'
1436
                    <a class="icon-toolbar" id="view-embedded"
1437
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1438
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1439
                    </a>
1440
                  </span>';
1441
        } else {
1442
            $navbar = '
1443
                 <span id="'.$barId.'" class="buttons text-right">
1444
                    '.$reportingIcon.'
1445
                    '.$previousIcon.'
1446
                    '.$nextIcon.'
1447
                </span>';
1448
        }
1449
1450
        return $navbar;
1451
    }
1452
1453
    /**
1454
     * Gets the next resource in queue (url).
1455
     *
1456
     * @return string URL to load into the viewer
1457
     */
1458
    public function get_next_index()
1459
    {
1460
        // TODO
1461
        $index = $this->index;
1462
        $index++;
1463
        while (
1464
            !empty($this->ordered_items[$index]) && ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) &&
1465
            $index < $this->max_ordered_items
1466
        ) {
1467
            $index++;
1468
            if ($index == $this->max_ordered_items) {
1469
                if ('dir' === $this->items[$this->ordered_items[$index]]->get_type()) {
1470
                    return $this->index;
1471
                }
1472
1473
                return $index;
1474
            }
1475
        }
1476
        if (empty($this->ordered_items[$index])) {
1477
            return $this->index;
1478
        }
1479
1480
        return $index;
1481
    }
1482
1483
    /**
1484
     * Gets item_id for the next element.
1485
     *
1486
     * @return int Next item (DB) ID
1487
     */
1488
    public function get_next_item_id()
1489
    {
1490
        $new_index = $this->get_next_index();
1491
        if (!empty($new_index)) {
1492
            if (isset($this->ordered_items[$new_index])) {
1493
                return $this->ordered_items[$new_index];
1494
            }
1495
        }
1496
1497
        return 0;
1498
    }
1499
1500
    /**
1501
     * Returns the package type ('scorm','aicc','scorm2004','ppt'...).
1502
     *
1503
     * Generally, the package provided is in the form of a zip file, so the function
1504
     * has been written to test a zip file. If not a zip, the function will return the
1505
     * default return value: ''
1506
     *
1507
     * @param string $filePath the path to the file
1508
     * @param string $file_name the original name of the file
1509
     *
1510
     * @return string 'scorm','aicc','scorm2004','error-empty-package'
1511
     *                if the package is empty, or '' if the package cannot be recognized
1512
     */
1513
    public static function getPackageType($filePath, $file_name)
1514
    {
1515
        // Get name of the zip file without the extension.
1516
        $file_info = pathinfo($file_name);
1517
        $extension = $file_info['extension']; // Extension only.
1518
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
1519
                'dll',
1520
                'exe',
1521
            ])) {
1522
            return 'oogie';
1523
        }
1524
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
1525
                'dll',
1526
                'exe',
1527
            ])) {
1528
            return 'woogie';
1529
        }
1530
1531
        $zipFile = new ZipFile();
1532
        $zipFile->openFile($filePath);
1533
        $zipContentArray = $zipFile->getEntries();
1534
        $package_type = '';
1535
        $manifest = '';
1536
        $aicc_match_crs = 0;
1537
        $aicc_match_au = 0;
1538
        $aicc_match_des = 0;
1539
        $aicc_match_cst = 0;
1540
        $countItems = 0;
1541
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
1542
        if ($zipContentArray) {
1543
            $countItems = count($zipContentArray);
1544
            if ($countItems > 0) {
1545
                foreach ($zipContentArray as $thisContent) {
1546
                    $fileName = basename($thisContent->getName());
1547
                    if (preg_match('~.(php.*|phtml)$~i', $fileName)) {
1548
                        // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
1549
                    } elseif (false !== stristr($fileName, 'imsmanifest.xml')) {
1550
                        $manifest = $fileName; // Just the relative directory inside scorm/
1551
                        $package_type = 'scorm';
1552
                        break; // Exit the foreach loop.
1553
                    } elseif (
1554
                        preg_match('/aicc\//i', $fileName) ||
1555
                        in_array(
1556
                            strtolower(pathinfo($fileName, PATHINFO_EXTENSION)),
1557
                            ['crs', 'au', 'des', 'cst']
1558
                        )
1559
                    ) {
1560
                        $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
1561
                        switch ($ext) {
1562
                            case 'crs':
1563
                                $aicc_match_crs = 1;
1564
                                break;
1565
                            case 'au':
1566
                                $aicc_match_au = 1;
1567
                                break;
1568
                            case 'des':
1569
                                $aicc_match_des = 1;
1570
                                break;
1571
                            case 'cst':
1572
                                $aicc_match_cst = 1;
1573
                                break;
1574
                            default:
1575
                                break;
1576
                        }
1577
                        //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
1578
                    } else {
1579
                        $package_type = '';
1580
                    }
1581
                }
1582
            }
1583
        }
1584
1585
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
1586
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
1587
            $package_type = 'aicc';
1588
        }
1589
1590
        // Try with chamilo course builder
1591
        if (empty($package_type)) {
1592
            // Sometimes users will try to upload an empty zip, or a zip with
1593
            // only a folder. Catch that and make the calling function aware.
1594
            // If the single file was the imsmanifest.xml, then $package_type
1595
            // would be 'scorm' and we wouldn't be here.
1596
            if ($countItems < 2) {
1597
                return 'error-empty-package';
1598
            }
1599
            $package_type = 'chamilo';
1600
        }
1601
1602
        return $package_type;
1603
    }
1604
1605
    /**
1606
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
1607
     *
1608
     * @return string URL to load into the viewer
1609
     */
1610
    public function get_previous_index()
1611
    {
1612
        $index = $this->index;
1613
        if (isset($this->ordered_items[$index - 1])) {
1614
            $index--;
1615
            while (isset($this->ordered_items[$index]) &&
1616
                ('dir' === $this->items[$this->ordered_items[$index]]->get_type())
1617
            ) {
1618
                $index--;
1619
                if ($index < 0) {
1620
                    return $this->index;
1621
                }
1622
            }
1623
        }
1624
1625
        return $index;
1626
    }
1627
1628
    /**
1629
     * Gets item_id for the next element.
1630
     *
1631
     * @return int Previous item (DB) ID
1632
     */
1633
    public function get_previous_item_id()
1634
    {
1635
        $index = $this->get_previous_index();
1636
1637
        return $this->ordered_items[$index];
1638
    }
1639
1640
    /**
1641
     * Returns the HTML necessary to print a mediaplayer block inside a page.
1642
     *
1643
     * @param int    $lpItemId
1644
     * @param string $autostart
1645
     *
1646
     * @return string The mediaplayer HTML
1647
     */
1648
    public function get_mediaplayer($lpItemId, $autostart = 'true')
1649
    {
1650
        $courseInfo = api_get_course_info();
1651
        $lpItemId = (int) $lpItemId;
1652
1653
        if (empty($courseInfo) || empty($lpItemId)) {
1654
            return '';
1655
        }
1656
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
1657
1658
        if (empty($item)) {
1659
            return '';
1660
        }
1661
1662
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1663
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1664
        $itemViewId = (int) $item->db_item_view_id;
1665
1666
        // Getting all the information about the item.
1667
        $sql = "SELECT lp_view.status
1668
                FROM $tbl_lp_item as lpi
1669
                INNER JOIN $tbl_lp_item_view as lp_view
1670
                ON (lpi.iid = lp_view.lp_item_id)
1671
                WHERE
1672
                    lp_view.iid = $itemViewId AND
1673
                    lpi.iid = $lpItemId
1674
                ";
1675
        $result = Database::query($sql);
1676
        $row = Database::fetch_assoc($result);
1677
        $output = '';
1678
        $audio = $item->audio;
1679
1680
        if (!empty($audio)) {
1681
            $list = $_SESSION['oLP']->get_toc();
1682
1683
            switch ($item->get_type()) {
1684
                case 'quiz':
1685
                    $type_quiz = false;
1686
                    foreach ($list as $toc) {
1687
                        if ($toc['id'] == $_SESSION['oLP']->current) {
1688
                            $type_quiz = true;
1689
                        }
1690
                    }
1691
1692
                    if ($type_quiz) {
1693
                        if (1 == $_SESSION['oLP']->prevent_reinit) {
1694
                            $autostart_audio = 'completed' === $row['status'] ? 'false' : 'true';
1695
                        } else {
1696
                            $autostart_audio = $autostart;
1697
                        }
1698
                    }
1699
                    break;
1700
                case TOOL_READOUT_TEXT:
1701
                    $autostart_audio = 'false';
1702
                    break;
1703
                default:
1704
                    $autostart_audio = 'true';
1705
            }
1706
1707
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'.$audio;
1708
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document'.$audio.'?'.api_get_cidreq();
1709
1710
            $player = Display::getMediaPlayer(
1711
                $file,
1712
                [
1713
                    'id' => 'lp_audio_media_player',
1714
                    'url' => $url,
1715
                    'autoplay' => $autostart_audio,
1716
                    'width' => '100%',
1717
                ]
1718
            );
1719
1720
            // The mp3 player.
1721
            $output = '<div id="container">';
1722
            $output .= $player;
1723
            $output .= '</div>';
1724
        }
1725
1726
        return $output;
1727
    }
1728
1729
    /**
1730
     * @param int    $studentId
1731
     * @param int    $prerequisite
1732
     * @param Course $course
1733
     * @param int    $sessionId
1734
     *
1735
     * @return bool
1736
     */
1737
    public static function isBlockedByPrerequisite(
1738
        $studentId,
1739
        $prerequisite,
1740
        Course $course,
1741
        $sessionId
1742
    ) {
1743
        if (null === $course) {
1744
            return false;
1745
        }
1746
1747
        $courseId = $course->getId();
1748
1749
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
1750
        if ($allow) {
1751
            if (api_is_allowed_to_edit() ||
1752
                api_is_platform_admin(true) ||
1753
                api_is_drh() ||
1754
                api_is_coach($sessionId, $courseId, false)
1755
            ) {
1756
                return false;
1757
            }
1758
        }
1759
1760
        $isBlocked = false;
1761
        if (!empty($prerequisite)) {
1762
            $progress = self::getProgress(
1763
                $prerequisite,
1764
                $studentId,
1765
                $courseId,
1766
                $sessionId
1767
            );
1768
            if ($progress < 100) {
1769
                $isBlocked = true;
1770
            }
1771
1772
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
1773
                // Block if it does not exceed minimum time
1774
                // Minimum time (in minutes) to pass the learning path
1775
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
1776
1777
                if ($accumulateWorkTime > 0) {
1778
                    // Total time in course (sum of times in learning paths from course)
1779
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
1780
1781
                    // Connect with the plugin_licences_course_session table
1782
                    // which indicates what percentage of the time applies
1783
                    // Minimum connection percentage
1784
                    $perc = 100;
1785
                    // Time from the course
1786
                    $tc = $accumulateWorkTimeTotal;
1787
1788
                    // Percentage of the learning paths
1789
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
1790
                    // Minimum time for each learning path
1791
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
1792
1793
                    // Spent time (in seconds) so far in the learning path
1794
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
1795
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
1796
1797
                    if ($lpTime < ($accumulateWorkTime * 60)) {
1798
                        $isBlocked = true;
1799
                    }
1800
                }
1801
            }
1802
        }
1803
1804
        return $isBlocked;
1805
    }
1806
1807
    /**
1808
     * Checks if the learning path is visible for student after the progress
1809
     * of its prerequisite is completed, considering the time availability and
1810
     * the LP visibility.
1811
     */
1812
    public static function is_lp_visible_for_student(CLp $lp, $student_id, Course $course, SessionEntity $session = null): bool
1813
    {
1814
        if (null === $course) {
1815
            return false;
1816
        }
1817
1818
        $sessionId = $session ? $session->getId() : 0;
1819
        $courseId = $course->getId();
1820
        $visibility = $lp->isVisible($course, $session);
1821
1822
        // If the item was deleted.
1823
        if (false === $visibility) {
1824
            return false;
1825
        }
1826
1827
        $now = time();
1828
        if ($lp->hasCategory()) {
1829
            $category = $lp->getCategory();
1830
1831
            if (false === self::categoryIsVisibleForStudent(
1832
                    $category,
1833
                    api_get_user_entity($student_id),
1834
                    $course,
1835
                    $session
1836
                )) {
1837
                return false;
1838
            }
1839
1840
            $prerequisite = $lp->getPrerequisite();
1841
            $is_visible = true;
1842
1843
            $isBlocked = self::isBlockedByPrerequisite(
1844
                $student_id,
1845
                $prerequisite,
1846
                $course,
1847
                $sessionId
1848
            );
1849
1850
            if ($isBlocked) {
1851
                $is_visible = false;
1852
            }
1853
1854
            // Also check the time availability of the LP
1855
            if ($is_visible) {
1856
                // Adding visibility restrictions
1857
                if (null !== $lp->getPublicatedOn()) {
1858
                    if ($now < $lp->getPublicatedOn()->getTimestamp()) {
1859
                        $is_visible = false;
1860
                    }
1861
                }
1862
                // Blocking empty start times see BT#2800
1863
                global $_custom;
1864
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
1865
                    $_custom['lps_hidden_when_no_start_date']
1866
                ) {
1867
                    if (null !== $lp->getPublicatedOn()) {
1868
                        $is_visible = false;
1869
                    }
1870
                }
1871
1872
                if (null !== $lp->getExpiredOn()) {
1873
                    if ($now > $lp->getExpiredOn()->getTimestamp()) {
1874
                        $is_visible = false;
1875
                    }
1876
                }
1877
            }
1878
1879
            if ($is_visible) {
1880
                $subscriptionSettings = self::getSubscriptionSettings();
1881
1882
                // Check if the subscription users/group to a LP is ON
1883
                if (1 == $lp->getSubscribeUsers() &&
1884
                    true === $subscriptionSettings['allow_add_users_to_lp']
1885
                ) {
1886
                    // Try group
1887
                    $is_visible = false;
1888
                    // Checking only the user visibility
1889
                    // @todo fix visibility
1890
                    $userVisibility = 1;
1891
                    if (1 == $userVisibility) {
1892
                        $is_visible = true;
1893
                    } else {
1894
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
1895
                        if (!empty($userGroups)) {
1896
                            foreach ($userGroups as $groupInfo) {
1897
                                $groupId = $groupInfo['iid'];
1898
                                // @todo fix visibility.
1899
                                $userVisibility = 1;
1900
                                if (1 == $userVisibility) {
1901
                                    $is_visible = true;
1902
                                    break;
1903
                                }
1904
                            }
1905
                        }
1906
                    }
1907
                }
1908
            }
1909
1910
            return $is_visible;
1911
        }
1912
1913
        return true;
1914
    }
1915
1916
    /**
1917
     * @param int $lpId
1918
     * @param int $userId
1919
     * @param int $courseId
1920
     * @param int $sessionId
1921
     *
1922
     * @return int
1923
     */
1924
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
1925
    {
1926
        $lpId = (int) $lpId;
1927
        $userId = (int) $userId;
1928
        $courseId = (int) $courseId;
1929
        $sessionId = (int) $sessionId;
1930
1931
        $sessionCondition = api_get_session_condition($sessionId);
1932
        $table = Database::get_course_table(TABLE_LP_VIEW);
1933
        $sql = "SELECT progress FROM $table
1934
                WHERE
1935
                    c_id = $courseId AND
1936
                    lp_id = $lpId AND
1937
                    user_id = $userId $sessionCondition ";
1938
        $res = Database::query($sql);
1939
1940
        $progress = 0;
1941
        if (Database::num_rows($res) > 0) {
1942
            $row = Database::fetch_array($res);
1943
            $progress = (int) $row['progress'];
1944
        }
1945
1946
        return $progress;
1947
    }
1948
1949
    /**
1950
     * @param array $lpList
1951
     * @param int   $userId
1952
     * @param int   $courseId
1953
     * @param int   $sessionId
1954
     *
1955
     * @return array
1956
     */
1957
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
1958
    {
1959
        $lpList = array_map('intval', $lpList);
1960
        if (empty($lpList)) {
1961
            return [];
1962
        }
1963
1964
        $lpList = implode("','", $lpList);
1965
1966
        $userId = (int) $userId;
1967
        $courseId = (int) $courseId;
1968
        $sessionId = (int) $sessionId;
1969
1970
        $sessionCondition = api_get_session_condition($sessionId);
1971
        $table = Database::get_course_table(TABLE_LP_VIEW);
1972
        $sql = "SELECT lp_id, progress FROM $table
1973
                WHERE
1974
                    c_id = $courseId AND
1975
                    lp_id IN ('".$lpList."') AND
1976
                    user_id = $userId $sessionCondition ";
1977
        $res = Database::query($sql);
1978
1979
        if (Database::num_rows($res) > 0) {
1980
            $list = [];
1981
            while ($row = Database::fetch_array($res)) {
1982
                $list[$row['lp_id']] = $row['progress'];
1983
            }
1984
1985
            return $list;
1986
        }
1987
1988
        return [];
1989
    }
1990
1991
    /**
1992
     * Displays a progress bar
1993
     * completed so far.
1994
     *
1995
     * @param int    $percentage Progress value to display
1996
     * @param string $text_add   Text to display near the progress value
1997
     *
1998
     * @return string HTML string containing the progress bar
1999
     */
2000
    public static function get_progress_bar($percentage = -1, $text_add = '')
2001
    {
2002
        $text = $percentage.$text_add;
2003
2004
        return '<div class="progress">
2005
            <div id="progress_bar_value"
2006
                class="progress-bar progress-bar-success" role="progressbar"
2007
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2008
            '.$text.'
2009
            </div>
2010
        </div>';
2011
    }
2012
2013
    /**
2014
     * @param string $mode can be '%' or 'abs'
2015
     *                     otherwise this value will be used $this->progress_bar_mode
2016
     *
2017
     * @return string
2018
     */
2019
    public function getProgressBar($mode = null)
2020
    {
2021
        [$percentage, $text_add] = $this->get_progress_bar_text($mode);
2022
2023
        return self::get_progress_bar($percentage, $text_add);
2024
    }
2025
2026
    /**
2027
     * Gets the progress bar info to display inside the progress bar.
2028
     * Also used by scorm_api.php.
2029
     *
2030
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2031
     *                     we display a number of completed elements per total elements
2032
     * @param int    $add  Additional steps to fake as completed
2033
     *
2034
     * @return array Percentage or number and symbol (% or /xx)
2035
     */
2036
    public function get_progress_bar_text($mode = '', $add = 0)
2037
    {
2038
        if (empty($mode)) {
2039
            $mode = $this->progress_bar_mode;
2040
        }
2041
        $text = '';
2042
        $percentage = 0;
2043
        // If the option to use the score as progress is set for this learning
2044
        // path, then the rules are completely different: we assume only one
2045
        // item exists and the progress of the LP depends on the score
2046
        $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
2047
        if (true === $scoreAsProgressSetting) {
2048
            $scoreAsProgress = $this->getUseScoreAsProgress();
2049
            if ($scoreAsProgress) {
2050
                // Get single item's score
2051
                $itemId = $this->get_current_item_id();
2052
                $item = $this->getItem($itemId);
2053
                $score = $item->get_score();
2054
                $maxScore = $item->get_max();
2055
                if ($mode = '%') {
2056
                    if (!empty($maxScore)) {
2057
                        $percentage = ((float) $score / (float) $maxScore) * 100;
2058
                    }
2059
                    $percentage = number_format($percentage, 0);
2060
                    $text = '%';
2061
                } else {
2062
                    $percentage = $score;
2063
                    $text = '/'.$maxScore;
2064
                }
2065
2066
                return [$percentage, $text];
2067
            }
2068
        }
2069
        // otherwise just continue the normal processing of progress
2070
        $total_items = $this->getTotalItemsCountWithoutDirs();
2071
        $completeItems = $this->get_complete_items_count();
2072
        if (0 != $add) {
2073
            $completeItems += $add;
2074
        }
2075
        if ($completeItems > $total_items) {
2076
            $completeItems = $total_items;
2077
        }
2078
        if ('%' === $mode) {
2079
            if ($total_items > 0) {
2080
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2081
            }
2082
            $percentage = number_format($percentage, 0);
2083
            $text = '%';
2084
        } elseif ('abs' === $mode) {
2085
            $percentage = $completeItems;
2086
            $text = '/'.$total_items;
2087
        }
2088
2089
        return [
2090
            $percentage,
2091
            $text,
2092
        ];
2093
    }
2094
2095
    /**
2096
     * Gets the progress bar mode.
2097
     *
2098
     * @return string The progress bar mode attribute
2099
     */
2100
    public function get_progress_bar_mode()
2101
    {
2102
        if (!empty($this->progress_bar_mode)) {
2103
            return $this->progress_bar_mode;
2104
        }
2105
2106
        return '%';
2107
    }
2108
2109
    /**
2110
     * Gets the learnpath theme (remote or local).
2111
     *
2112
     * @return string Learnpath theme
2113
     */
2114
    public function get_theme()
2115
    {
2116
        if (!empty($this->theme)) {
2117
            return $this->theme;
2118
        }
2119
2120
        return '';
2121
    }
2122
2123
    /**
2124
     * Gets the learnpath session id.
2125
     *
2126
     * @return int
2127
     */
2128
    public function get_lp_session_id()
2129
    {
2130
        if (!empty($this->lp_session_id)) {
2131
            return (int) $this->lp_session_id;
2132
        }
2133
2134
        return 0;
2135
    }
2136
2137
    /**
2138
     * Generate a new prerequisites string for a given item. If this item was a sco and
2139
     * its prerequisites were strings (instead of IDs), then transform those strings into
2140
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2141
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2142
     * same rule as the scormExport() method.
2143
     *
2144
     * @param int $item_id Item ID
2145
     *
2146
     * @return string Prerequisites string ready for the export as SCORM
2147
     */
2148
    public function get_scorm_prereq_string($item_id)
2149
    {
2150
        if ($this->debug > 0) {
2151
            error_log('In learnpath::get_scorm_prereq_string()');
2152
        }
2153
        if (!is_object($this->items[$item_id])) {
2154
            return false;
2155
        }
2156
        /** @var learnpathItem $oItem */
2157
        $oItem = $this->items[$item_id];
2158
        $prereq = $oItem->get_prereq_string();
2159
2160
        if (empty($prereq)) {
2161
            return '';
2162
        }
2163
        if (preg_match('/^\d+$/', $prereq) &&
2164
            isset($this->items[$prereq]) &&
2165
            is_object($this->items[$prereq])
2166
        ) {
2167
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2168
            // then simply return it (with the ITEM_ prefix).
2169
            //return 'ITEM_' . $prereq;
2170
            return $this->items[$prereq]->ref;
2171
        } else {
2172
            if (isset($this->refs_list[$prereq])) {
2173
                // It's a simple string item from which the ID can be found in the refs list,
2174
                // so we can transform it directly to an ID for export.
2175
                return $this->items[$this->refs_list[$prereq]]->ref;
2176
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2177
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2178
            } else {
2179
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2180
                // and replace them, one by one, by the internal IDs (chamilo db)
2181
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2182
                // by a space as well.
2183
                $find = [
2184
                    '&',
2185
                    '|',
2186
                    '~',
2187
                    '=',
2188
                    '<>',
2189
                    '{',
2190
                    '}',
2191
                    '*',
2192
                    '(',
2193
                    ')',
2194
                ];
2195
                $replace = [
2196
                    ' ',
2197
                    ' ',
2198
                    ' ',
2199
                    ' ',
2200
                    ' ',
2201
                    ' ',
2202
                    ' ',
2203
                    ' ',
2204
                    ' ',
2205
                    ' ',
2206
                ];
2207
                $prereq_mod = str_replace($find, $replace, $prereq);
2208
                $ids = explode(' ', $prereq_mod);
2209
                foreach ($ids as $id) {
2210
                    $id = trim($id);
2211
                    if (isset($this->refs_list[$id])) {
2212
                        $prereq = preg_replace(
2213
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2214
                            'ITEM_'.$this->refs_list[$id],
2215
                            $prereq
2216
                        );
2217
                    }
2218
                }
2219
2220
                return $prereq;
2221
            }
2222
        }
2223
    }
2224
2225
    /**
2226
     * Returns the XML DOM document's node.
2227
     *
2228
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2229
     * @param string   $id       The identifier to look for
2230
     *
2231
     * @return mixed The reference to the element found with that identifier. False if not found
2232
     */
2233
    public function get_scorm_xml_node(&$children, $id)
2234
    {
2235
        for ($i = 0; $i < $children->length; $i++) {
2236
            $item_temp = $children->item($i);
2237
            if ('item' === $item_temp->nodeName) {
2238
                if ($item_temp->getAttribute('identifier') == $id) {
2239
                    return $item_temp;
2240
                }
2241
            }
2242
            $subchildren = $item_temp->childNodes;
2243
            if ($subchildren && $subchildren->length > 0) {
2244
                $val = $this->get_scorm_xml_node($subchildren, $id);
2245
                if (is_object($val)) {
2246
                    return $val;
2247
                }
2248
            }
2249
        }
2250
2251
        return false;
2252
    }
2253
2254
    /**
2255
     * Gets the status list for all LP's items.
2256
     *
2257
     * @return array Array of [index] => [item ID => current status]
2258
     */
2259
    public function get_items_status_list()
2260
    {
2261
        $list = [];
2262
        foreach ($this->ordered_items as $item_id) {
2263
            $list[] = [
2264
                $item_id => $this->items[$item_id]->get_status(),
2265
            ];
2266
        }
2267
2268
        return $list;
2269
    }
2270
2271
    /**
2272
     * Return the number of interactions for the given learnpath Item View ID.
2273
     * This method can be used as static.
2274
     *
2275
     * @param int $lp_iv_id  Item View ID
2276
     * @param int $course_id course id
2277
     *
2278
     * @return int
2279
     */
2280
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2281
    {
2282
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2283
        $lp_iv_id = (int) $lp_iv_id;
2284
        $course_id = (int) $course_id;
2285
2286
        $sql = "SELECT count(*) FROM $table
2287
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2288
        $res = Database::query($sql);
2289
        $num = 0;
2290
        if (Database::num_rows($res)) {
2291
            $row = Database::fetch_array($res);
2292
            $num = $row[0];
2293
        }
2294
2295
        return $num;
2296
    }
2297
2298
    /**
2299
     * Return the interactions as an array for the given lp_iv_id.
2300
     * This method can be used as static.
2301
     *
2302
     * @param int $lp_iv_id Learnpath Item View ID
2303
     *
2304
     * @return array
2305
     *
2306
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2307
     */
2308
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2309
    {
2310
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2311
        $list = [];
2312
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2313
        $lp_iv_id = (int) $lp_iv_id;
2314
2315
        if (empty($lp_iv_id) || empty($course_id)) {
2316
            return [];
2317
        }
2318
2319
        $sql = "SELECT * FROM $table
2320
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2321
                ORDER BY order_id ASC";
2322
        $res = Database::query($sql);
2323
        $num = Database::num_rows($res);
2324
        if ($num > 0) {
2325
            $list[] = [
2326
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2327
                'id' => api_htmlentities(get_lang('Interaction ID'), ENT_QUOTES),
2328
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2329
                'time' => api_htmlentities(get_lang('Time (finished at...)'), ENT_QUOTES),
2330
                'correct_responses' => api_htmlentities(get_lang('Correct answers'), ENT_QUOTES),
2331
                'student_response' => api_htmlentities(get_lang('Learner answers'), ENT_QUOTES),
2332
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2333
                'latency' => api_htmlentities(get_lang('Time spent'), ENT_QUOTES),
2334
                'student_response_formatted' => '',
2335
            ];
2336
            while ($row = Database::fetch_array($res)) {
2337
                $studentResponseFormatted = urldecode($row['student_response']);
2338
                $content_student_response = explode('__|', $studentResponseFormatted);
2339
                if (count($content_student_response) > 0) {
2340
                    if (count($content_student_response) >= 3) {
2341
                        // Pop the element off the end of array.
2342
                        array_pop($content_student_response);
2343
                    }
2344
                    $studentResponseFormatted = implode(',', $content_student_response);
2345
                }
2346
2347
                $list[] = [
2348
                    'order_id' => $row['order_id'] + 1,
2349
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2350
                    'type' => $row['interaction_type'],
2351
                    'time' => $row['completion_time'],
2352
                    'correct_responses' => '', // Hide correct responses from students.
2353
                    'student_response' => $row['student_response'],
2354
                    'result' => $row['result'],
2355
                    'latency' => $row['latency'],
2356
                    'student_response_formatted' => $studentResponseFormatted,
2357
                ];
2358
            }
2359
        }
2360
2361
        return $list;
2362
    }
2363
2364
    /**
2365
     * Return the number of objectives for the given learnpath Item View ID.
2366
     * This method can be used as static.
2367
     *
2368
     * @param int $lp_iv_id  Item View ID
2369
     * @param int $course_id Course ID
2370
     *
2371
     * @return int Number of objectives
2372
     */
2373
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2374
    {
2375
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2376
        $course_id = (int) $course_id;
2377
        $lp_iv_id = (int) $lp_iv_id;
2378
        $sql = "SELECT count(*) FROM $table
2379
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2380
        //@todo seems that this always returns 0
2381
        $res = Database::query($sql);
2382
        $num = 0;
2383
        if (Database::num_rows($res)) {
2384
            $row = Database::fetch_array($res);
2385
            $num = $row[0];
2386
        }
2387
2388
        return $num;
2389
    }
2390
2391
    /**
2392
     * Return the objectives as an array for the given lp_iv_id.
2393
     * This method can be used as static.
2394
     *
2395
     * @param int $lpItemViewId Learnpath Item View ID
2396
     * @param int $course_id
2397
     *
2398
     * @return array
2399
     *
2400
     * @todo    Translate labels
2401
     */
2402
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
2403
    {
2404
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2405
        $lpItemViewId = (int) $lpItemViewId;
2406
2407
        if (empty($course_id) || empty($lpItemViewId)) {
2408
            return [];
2409
        }
2410
2411
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2412
        $sql = "SELECT * FROM $table
2413
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
2414
                ORDER BY order_id ASC";
2415
        $res = Database::query($sql);
2416
        $num = Database::num_rows($res);
2417
        $list = [];
2418
        if ($num > 0) {
2419
            $list[] = [
2420
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2421
                'objective_id' => api_htmlentities(get_lang('Objective ID'), ENT_QUOTES),
2422
                'score_raw' => api_htmlentities(get_lang('Objective raw score'), ENT_QUOTES),
2423
                'score_max' => api_htmlentities(get_lang('Objective max score'), ENT_QUOTES),
2424
                'score_min' => api_htmlentities(get_lang('Objective min score'), ENT_QUOTES),
2425
                'status' => api_htmlentities(get_lang('Objective status'), ENT_QUOTES),
2426
            ];
2427
            while ($row = Database::fetch_array($res)) {
2428
                $list[] = [
2429
                    'order_id' => $row['order_id'] + 1,
2430
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
2431
                    'score_raw' => $row['score_raw'],
2432
                    'score_max' => $row['score_max'],
2433
                    'score_min' => $row['score_min'],
2434
                    'status' => $row['status'],
2435
                ];
2436
            }
2437
        }
2438
2439
        return $list;
2440
    }
2441
2442
    /**
2443
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
2444
     * used by get_html_toc() to be ready to display.
2445
     *
2446
     * @return array TOC as a table with 4 elements per row: title, link, status and level
2447
     */
2448
    public function get_toc()
2449
    {
2450
        $toc = [];
2451
        foreach ($this->ordered_items as $item_id) {
2452
            // TODO: Change this link generation and use new function instead.
2453
            $toc[] = [
2454
                'id' => $item_id,
2455
                'title' => $this->items[$item_id]->get_title(),
2456
                'status' => $this->items[$item_id]->get_status(),
2457
                'status_class' => self::getStatusCSSClassName($this->items[$item_id]->get_status()),
2458
                'level' => $this->items[$item_id]->get_level(),
2459
                'type' => $this->items[$item_id]->get_type(),
2460
                'description' => $this->items[$item_id]->get_description(),
2461
                'path' => $this->items[$item_id]->get_path(),
2462
                'parent' => $this->items[$item_id]->get_parent(),
2463
            ];
2464
        }
2465
2466
        return $toc;
2467
    }
2468
2469
    /**
2470
     * Returns the CSS class name associated with a given item status.
2471
     *
2472
     * @param $status string an item status
2473
     *
2474
     * @return string CSS class name
2475
     */
2476
    public static function getStatusCSSClassName($status)
2477
    {
2478
        if (array_key_exists($status, self::STATUS_CSS_CLASS_NAME)) {
2479
            return self::STATUS_CSS_CLASS_NAME[$status];
2480
        }
2481
2482
        return '';
2483
    }
2484
2485
    /**
2486
     * Generate and return the table of contents for this learnpath. The JS
2487
     * table returned is used inside of scorm_api.php.
2488
     *
2489
     * @param string $varname
2490
     *
2491
     * @return string A JS array variable construction
2492
     */
2493
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
2494
    {
2495
        $toc = $varname.' = new Array();';
2496
        foreach ($this->ordered_items as $item_id) {
2497
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
2498
        }
2499
2500
        return $toc;
2501
    }
2502
2503
    /**
2504
     * Gets the learning path type.
2505
     *
2506
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
2507
     *
2508
     * @return mixed Type ID or name, depending on the parameter
2509
     */
2510
    public function get_type($get_name = false)
2511
    {
2512
        $res = false;
2513
        if (!empty($this->type) && (!$get_name)) {
2514
            $res = $this->type;
2515
        }
2516
2517
        return $res;
2518
    }
2519
2520
    /**
2521
     * Gets the learning path type as static method.
2522
     *
2523
     * @param int $lp_id
2524
     *
2525
     * @return mixed Type ID or name, depending on the parameter
2526
     */
2527
    public static function get_type_static($lp_id = 0)
2528
    {
2529
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
2530
        $lp_id = (int) $lp_id;
2531
        $sql = "SELECT lp_type FROM $tbl_lp
2532
                WHERE iid = $lp_id";
2533
        $res = Database::query($sql);
2534
        if (false === $res) {
2535
            return null;
2536
        }
2537
        if (Database::num_rows($res) <= 0) {
2538
            return null;
2539
        }
2540
        $row = Database::fetch_array($res);
2541
2542
        return $row['lp_type'];
2543
    }
2544
2545
    /**
2546
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
2547
     * This method can be used as abstract and is recursive.
2548
     *
2549
     * @param CLp $lp
2550
     * @param int $parent    Parent ID of the items to look for
2551
     *
2552
     * @return array Ordered list of item IDs (empty array on error)
2553
     */
2554
    public static function get_flat_ordered_items_list(CLp $lp, $parent = 0)
2555
    {
2556
        $parent = (int) $parent;
2557
        $lpItemRepo = Container::getLpItemRepository();
2558
        if (empty($parent)) {
2559
            $rootItem = $lpItemRepo->getRootItem($lp->getIid());
2560
            if (null !== $rootItem) {
2561
                $parent = $rootItem->getIid();
2562
            }
2563
        }
2564
2565
        if (empty($parent)) {
2566
            return [];
2567
        }
2568
2569
        $criteria = new Criteria();
2570
        $criteria
2571
            ->where($criteria->expr()->neq('path', 'root'))
2572
            ->orderBy(
2573
                [
2574
                    'displayOrder' => Criteria::ASC,
2575
                ]
2576
            );
2577
        $items = $lp->getItems()->matching($criteria);
2578
        $items = $items->filter(
2579
            function (CLpItem $element) use ($parent) {
2580
                if ('root' === $element->getPath()) {
2581
                    return false;
2582
                }
2583
2584
                if (null !== $element->getParent()) {
2585
                    return $element->getParent()->getIid() === $parent;
2586
                }
2587
                return false;
2588
2589
            }
2590
        );
2591
2592
        /*
2593
         $lp = (int) $lp;
2594
        $parent = (int) $parent;
2595
2596
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2597
        $sql = "SELECT iid FROM $tbl_lp_item
2598
                WHERE lp_id = $lp AND parent_item_id = $parent
2599
                ORDER BY display_order";
2600
2601
        $res = Database::query($sql);
2602
        while ($row = Database::fetch_array($res)) {
2603
            $sublist = self::get_flat_ordered_items_list(
2604
                $lp,
2605
                $row['iid'],
2606
                $course_id
2607
            );
2608
            $list[] = $row['iid'];
2609
            foreach ($sublist as $item) {
2610
                $list[] = $item;
2611
            }
2612
        }
2613
        */
2614
2615
        $list = [];
2616
        foreach ($items as $item) {
2617
            $itemId = $item->getIid();
2618
            $sublist = self::get_flat_ordered_items_list($lp, $itemId);
2619
            $list[] = $itemId;
2620
            foreach ($sublist as $subItem) {
2621
                $list[] = $subItem;
2622
            }
2623
        }
2624
2625
        return $list;
2626
    }
2627
2628
    public static function getChapterTypes(): array
2629
    {
2630
        return [
2631
            'dir',
2632
        ];
2633
    }
2634
2635
    /**
2636
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
2637
     *
2638
     * @param array $toc_list
2639
     *
2640
     * @return array HTML TOC ready to display
2641
     */
2642
    public function getListArrayToc()
2643
    {
2644
        $lpItemRepo = Container::getLpItemRepository();
2645
        $itemRoot = $lpItemRepo->getRootItem($this->get_id());
2646
        $options = [
2647
            'decorate' => false,
2648
        ];
2649
2650
        return $lpItemRepo->childrenHierarchy($itemRoot, false, $options);
2651
    }
2652
2653
    /**
2654
     * Returns an HTML-formatted string ready to display with teacher buttons
2655
     * in LP view menu.
2656
     *
2657
     * @return string HTML TOC ready to display
2658
     */
2659
    public function get_teacher_toc_buttons()
2660
    {
2661
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
2662
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
2663
        $html = '';
2664
        if ($isAllow && false == $hideIcons) {
2665
            if ($this->get_lp_session_id() == api_get_session_id()) {
2666
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
2667
                $html .= '<div class="flex flex-row justify-center">';
2668
                $html .= "<a
2669
                    class='btn btn-sm btn-default'
2670
                    href='lp_controller.php?".api_get_cidreq()."&action=build&lp_id=".$this->lp_id."&isStudentView=false'
2671
                    target='_parent'>".
2672
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
2673
                $html .= "<a
2674
                    class='btn btn-sm btn-default'
2675
                    href='lp_controller.php?".api_get_cidreq()."&action=add_item&type=step&lp_id=".$this->lp_id."&isStudentView=false'
2676
                    target='_parent'>".
2677
                    Display::returnFontAwesomeIcon('pencil-alt').get_lang('Edit')."</a>";
2678
                $html .= '<a
2679
                    class="btn btn-sm btn-default"
2680
                    href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
2681
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
2682
                $html .= '</div>';
2683
                $html .= '</div>';
2684
            }
2685
        }
2686
2687
        return $html;
2688
    }
2689
2690
    /**
2691
     * Gets the learnpath name/title.
2692
     *
2693
     * @return string Learnpath name/title
2694
     */
2695
    public function get_name()
2696
    {
2697
        if (!empty($this->name)) {
2698
            return $this->name;
2699
        }
2700
2701
        return 'N/A';
2702
    }
2703
2704
    /**
2705
     * @return string
2706
     */
2707
    public function getNameNoTags()
2708
    {
2709
        return strip_tags($this->get_name());
2710
    }
2711
2712
    /**
2713
     * Gets a link to the resource from the present location, depending on item ID.
2714
     *
2715
     * @param string $type         Type of link expected
2716
     * @param int    $item_id      Learnpath item ID
2717
     * @param bool   $provided_toc
2718
     *
2719
     * @return string $provided_toc Link to the lp_item resource
2720
     */
2721
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
2722
    {
2723
        $course_id = $this->get_course_int_id();
2724
        $item_id = (int) $item_id;
2725
2726
        if (empty($item_id)) {
2727
            $item_id = $this->get_current_item_id();
2728
2729
            if (empty($item_id)) {
2730
                //still empty, this means there was no item_id given and we are not in an object context or
2731
                //the object property is empty, return empty link
2732
                $this->first();
2733
2734
                return '';
2735
            }
2736
        }
2737
2738
        $file = '';
2739
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
2740
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
2741
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2742
2743
        $sql = "SELECT
2744
                    l.lp_type as ltype,
2745
                    l.path as lpath,
2746
                    li.item_type as litype,
2747
                    li.path as lipath,
2748
                    li.parameters as liparams
2749
        		FROM $lp_table l
2750
                INNER JOIN $lp_item_table li
2751
                ON (li.lp_id = l.iid)
2752
        		WHERE
2753
        		    li.iid = $item_id
2754
        		";
2755
        $res = Database::query($sql);
2756
        if (Database::num_rows($res) > 0) {
2757
            $row = Database::fetch_array($res);
2758
            $lp_type = $row['ltype'];
2759
            $lp_path = $row['lpath'];
2760
            $lp_item_type = $row['litype'];
2761
            $lp_item_path = $row['lipath'];
2762
            $lp_item_params = $row['liparams'];
2763
            if (empty($lp_item_params) && false !== strpos($lp_item_path, '?')) {
2764
                [$lp_item_path, $lp_item_params] = explode('?', $lp_item_path);
2765
            }
2766
            //$sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
2767
            if ('http' === $type) {
2768
                //web path
2769
                //$course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
2770
            } else {
2771
                //$course_path = $sys_course_path; //system path
2772
            }
2773
2774
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
2775
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
2776
            if (in_array(
2777
                $lp_item_type,
2778
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
2779
            )
2780
            ) {
2781
                $lp_type = CLp::LP_TYPE;
2782
            }
2783
2784
            // Now go through the specific cases to get the end of the path
2785
            // @todo Use constants instead of int values.
2786
            switch ($lp_type) {
2787
                case CLp::LP_TYPE:
2788
                    $file = self::rl_get_resource_link_for_learnpath(
2789
                        $course_id,
2790
                        $this->get_id(),
2791
                        $item_id,
2792
                        $this->get_view_id()
2793
                    );
2794
                    switch ($lp_item_type) {
2795
                        case 'document':
2796
                            // Shows a button to download the file instead of just downloading the file directly.
2797
                            $documentPathInfo = pathinfo($file);
2798
                            if (isset($documentPathInfo['extension'])) {
2799
                                $parsed = parse_url($documentPathInfo['extension']);
2800
                                if (isset($parsed['path'])) {
2801
                                    $extension = $parsed['path'];
2802
                                    $extensionsToDownload = [
2803
                                        'zip',
2804
                                        'ppt',
2805
                                        'pptx',
2806
                                        'ods',
2807
                                        'xlsx',
2808
                                        'xls',
2809
                                        'csv',
2810
                                        'doc',
2811
                                        'docx',
2812
                                        'dot',
2813
                                    ];
2814
2815
                                    if (in_array($extension, $extensionsToDownload)) {
2816
                                        $file = api_get_path(WEB_CODE_PATH).
2817
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
2818
                                    }
2819
                                }
2820
                            }
2821
                            break;
2822
                        case 'dir':
2823
                            $file = 'lp_content.php?type=dir';
2824
                            break;
2825
                        case 'link':
2826
                            if (Link::is_youtube_link($file)) {
2827
                                $src = Link::get_youtube_video_id($file);
2828
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
2829
                            } elseif (Link::isVimeoLink($file)) {
2830
                                $src = Link::getVimeoLinkId($file);
2831
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
2832
                            } else {
2833
                                // If the current site is HTTPS and the link is
2834
                                // HTTP, browsers will refuse opening the link
2835
                                $urlId = api_get_current_access_url_id();
2836
                                $url = api_get_access_url($urlId, false);
2837
                                $protocol = substr($url['url'], 0, 5);
2838
                                if ('https' === $protocol) {
2839
                                    $linkProtocol = substr($file, 0, 5);
2840
                                    if ('http:' === $linkProtocol) {
2841
                                        //this is the special intervention case
2842
                                        $file = api_get_path(WEB_CODE_PATH).
2843
                                            'lp/embed.php?type=nonhttps&source='.urlencode($file);
2844
                                    }
2845
                                }
2846
                            }
2847
                            break;
2848
                        case 'quiz':
2849
                            // Check how much attempts of a exercise exits in lp
2850
                            $lp_item_id = $this->get_current_item_id();
2851
                            $lp_view_id = $this->get_view_id();
2852
2853
                            $prevent_reinit = null;
2854
                            if (isset($this->items[$this->current])) {
2855
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
2856
                            }
2857
2858
                            if (empty($provided_toc)) {
2859
                                $list = $this->get_toc();
2860
                            } else {
2861
                                $list = $provided_toc;
2862
                            }
2863
2864
                            $type_quiz = false;
2865
                            foreach ($list as $toc) {
2866
                                if ($toc['id'] == $lp_item_id && 'quiz' === $toc['type']) {
2867
                                    $type_quiz = true;
2868
                                }
2869
                            }
2870
2871
                            if ($type_quiz) {
2872
                                $lp_item_id = (int) $lp_item_id;
2873
                                $lp_view_id = (int) $lp_view_id;
2874
                                $sql = "SELECT count(*) FROM $lp_item_view_table
2875
                                        WHERE
2876
                                            lp_item_id='".$lp_item_id."' AND
2877
                                            lp_view_id ='".$lp_view_id."' AND
2878
                                            status='completed'";
2879
                                $result = Database::query($sql);
2880
                                $row_count = Database:: fetch_row($result);
2881
                                $count_item_view = (int) $row_count[0];
2882
                                $not_multiple_attempt = 0;
2883
                                if (1 === $prevent_reinit && $count_item_view > 0) {
2884
                                    $not_multiple_attempt = 1;
2885
                                }
2886
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
2887
                            }
2888
                            break;
2889
                    }
2890
2891
                    $tmp_array = explode('/', $file);
2892
                    $document_name = $tmp_array[count($tmp_array) - 1];
2893
                    if (strpos($document_name, '_DELETED_')) {
2894
                        $file = 'blank.php?error=document_deleted';
2895
                    }
2896
                    break;
2897
                case CLp::SCORM_TYPE:
2898
                    if ('dir' !== $lp_item_type) {
2899
                        // Quite complex here:
2900
                        // We want to make sure 'http://' (and similar) links can
2901
                        // be loaded as is (withouth the Chamilo path in front) but
2902
                        // some contents use this form: resource.htm?resource=http://blablabla
2903
                        // which means we have to find a protocol at the path's start, otherwise
2904
                        // it should not be considered as an external URL.
2905
                        // if ($this->prerequisites_match($item_id)) {
2906
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
2907
                            if ($this->debug > 2) {
2908
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
2909
                            }
2910
                            // Distant url, return as is.
2911
                            $file = $lp_item_path;
2912
                        } else {
2913
                            if ($this->debug > 2) {
2914
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path);
2915
                            }
2916
                            // Prevent getting untranslatable urls.
2917
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
2918
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
2919
2920
                            /*$asset = $this->getEntity()->getAsset();
2921
                            $folder = Container::getAssetRepository()->getFolder($asset);
2922
                            $hasFile = Container::getAssetRepository()->getFileSystem()->has($folder.$lp_item_path);
2923
                            $file = null;
2924
                            if ($hasFile) {
2925
                                $file = Container::getAssetRepository()->getAssetUrl($asset).'/'.$lp_item_path;
2926
                            }*/
2927
                            $file = $this->scormUrl.$lp_item_path;
2928
2929
                            // Prepare the path.
2930
                            /*$file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
2931
                            // TODO: Fix this for urls with protocol header.
2932
                            $file = str_replace('//', '/', $file);
2933
                            $file = str_replace(':/', '://', $file);
2934
                            if ('/' === substr($lp_path, -1)) {
2935
                                $lp_path = substr($lp_path, 0, -1);
2936
                            }*/
2937
                            /*if (!$hasFile) {
2938
                                // if file not found.
2939
                                $decoded = html_entity_decode($lp_item_path);
2940
                                [$decoded] = explode('?', $decoded);
2941
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
2942
                                    $file = self::rl_get_resource_link_for_learnpath(
2943
                                        $course_id,
2944
                                        $this->get_id(),
2945
                                        $item_id,
2946
                                        $this->get_view_id()
2947
                                    );
2948
                                    if (empty($file)) {
2949
                                        $file = 'blank.php?error=document_not_found';
2950
                                    } else {
2951
                                        $tmp_array = explode('/', $file);
2952
                                        $document_name = $tmp_array[count($tmp_array) - 1];
2953
                                        if (strpos($document_name, '_DELETED_')) {
2954
                                            $file = 'blank.php?error=document_deleted';
2955
                                        } else {
2956
                                            $file = 'blank.php?error=document_not_found';
2957
                                        }
2958
                                    }
2959
                                } else {
2960
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
2961
                                }
2962
                            }*/
2963
                        }
2964
2965
                        // We want to use parameters if they were defined in the imsmanifest
2966
                        if (false === strpos($file, 'blank.php')) {
2967
                            $lp_item_params = ltrim($lp_item_params, '?');
2968
                            $file .= (false === strstr($file, '?') ? '?' : '').$lp_item_params;
2969
                        }
2970
                    } else {
2971
                        $file = 'lp_content.php?type=dir';
2972
                    }
2973
                    break;
2974
                case CLp::AICC_TYPE:
2975
                    // Formatting AICC HACP append URL.
2976
                    $aicc_append = '?aicc_sid='.
2977
                        urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
2978
                    if (!empty($lp_item_params)) {
2979
                        $aicc_append .= $lp_item_params.'&';
2980
                    }
2981
                    if ('dir' !== $lp_item_type) {
2982
                        // Quite complex here:
2983
                        // We want to make sure 'http://' (and similar) links can
2984
                        // be loaded as is (withouth the Chamilo path in front) but
2985
                        // some contents use this form: resource.htm?resource=http://blablabla
2986
                        // which means we have to find a protocol at the path's start, otherwise
2987
                        // it should not be considered as an external URL.
2988
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
2989
                            if ($this->debug > 2) {
2990
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
2991
                            }
2992
                            // Distant url, return as is.
2993
                            $file = $lp_item_path;
2994
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
2995
                            /*
2996
                            if (stristr($file,'<servername>') !== false) {
2997
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
2998
                            }
2999
                            */
3000
                            if (false !== stripos($file, '<servername>')) {
3001
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3002
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3003
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3004
                            }
3005
3006
                            $file .= $aicc_append;
3007
                        } else {
3008
                            if ($this->debug > 2) {
3009
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3010
                            }
3011
                            // Prevent getting untranslatable urls.
3012
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3013
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3014
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3015
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3016
                            // TODO: Fix this for urls with protocol header.
3017
                            $file = str_replace('//', '/', $file);
3018
                            $file = str_replace(':/', '://', $file);
3019
                            $file .= $aicc_append;
3020
                        }
3021
                    } else {
3022
                        $file = 'lp_content.php?type=dir';
3023
                    }
3024
                    break;
3025
                case 4:
3026
                default:
3027
                    break;
3028
            }
3029
            // Replace &amp; by & because &amp; will break URL with params
3030
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3031
        }
3032
        if ($this->debug > 2) {
3033
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3034
        }
3035
3036
        return $file;
3037
    }
3038
3039
    /**
3040
     * Gets the latest usable view or generate a new one.
3041
     *
3042
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3043
     * @param int $userId      The user ID, as $this->get_user_id() is not always available
3044
     *
3045
     * @return int DB lp_view id
3046
     */
3047
    public function get_view($attempt_num = 0, $userId = null)
3048
    {
3049
        $search = '';
3050
        $attempt_num = (int) $attempt_num;
3051
        // Use $attempt_num to enable multi-views management (disabled so far).
3052
        if (!empty($attempt_num)) {
3053
            $search = 'AND view_count = '.$attempt_num;
3054
        }
3055
3056
        $course_id = api_get_course_int_id();
3057
        $sessionId = api_get_session_id();
3058
3059
        // Check user ID.
3060
        if (empty($userId)) {
3061
            if (empty($this->get_user_id())) {
3062
                $this->error = 'User ID is empty in learnpath::get_view()';
3063
3064
                return null;
3065
            } else {
3066
                $userId = $this->get_user_id();
3067
            }
3068
        }
3069
        $sessionCondition = api_get_session_condition($sessionId);
3070
3071
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3072
        $table = Database::get_course_table(TABLE_LP_VIEW);
3073
        $sql = "SELECT iid FROM $table
3074
        		WHERE
3075
        		    c_id = $course_id AND
3076
        		    lp_id = ".$this->get_id()." AND
3077
        		    user_id = ".$userId."
3078
        		    $sessionCondition
3079
        		    $search
3080
                ORDER BY view_count DESC";
3081
        $res = Database::query($sql);
3082
        if (Database::num_rows($res) > 0) {
3083
            $row = Database::fetch_array($res);
3084
            $this->lp_view_id = $row['iid'];
3085
        } elseif (!api_is_invitee()) {
3086
            $params = [
3087
                'c_id' => $course_id,
3088
                'lp_id' => $this->get_id(),
3089
                'user_id' => $this->get_user_id(),
3090
                'view_count' => 1,
3091
                'last_item' => 0,
3092
            ];
3093
            if (!empty($sessionId)) {
3094
                $params['session_id']  = $sessionId;
3095
            }
3096
            $this->lp_view_id = Database::insert($table, $params);
3097
        }
3098
3099
        return $this->lp_view_id;
3100
    }
3101
3102
    /**
3103
     * Gets the current view id.
3104
     *
3105
     * @return int View ID (from lp_view)
3106
     */
3107
    public function get_view_id()
3108
    {
3109
        if (!empty($this->lp_view_id)) {
3110
            return (int) $this->lp_view_id;
3111
        }
3112
3113
        return 0;
3114
    }
3115
3116
    /**
3117
     * Gets the update queue.
3118
     *
3119
     * @return array Array containing IDs of items to be updated by JavaScript
3120
     */
3121
    public function get_update_queue()
3122
    {
3123
        return $this->update_queue;
3124
    }
3125
3126
    /**
3127
     * Gets the user ID.
3128
     *
3129
     * @return int User ID
3130
     */
3131
    public function get_user_id()
3132
    {
3133
        if (!empty($this->user_id)) {
3134
            return (int) $this->user_id;
3135
        }
3136
3137
        return false;
3138
    }
3139
3140
    /**
3141
     * Checks if any of the items has an audio element attached.
3142
     *
3143
     * @return bool True or false
3144
     */
3145
    public function has_audio()
3146
    {
3147
        $has = false;
3148
        foreach ($this->items as $i => $item) {
3149
            if (!empty($this->items[$i]->audio)) {
3150
                $has = true;
3151
                break;
3152
            }
3153
        }
3154
3155
        return $has;
3156
    }
3157
3158
    /**
3159
     * Updates learnpath attributes to point to the next element
3160
     * The last part is similar to set_current_item but processing the other way around.
3161
     */
3162
    public function next()
3163
    {
3164
        if ($this->debug > 0) {
3165
            error_log('In learnpath::next()', 0);
3166
        }
3167
        $this->last = $this->get_current_item_id();
3168
        $this->items[$this->last]->save(
3169
            false,
3170
            $this->prerequisites_match($this->last)
3171
        );
3172
        $this->autocomplete_parents($this->last);
3173
        $new_index = $this->get_next_index();
3174
        if ($this->debug > 2) {
3175
            error_log('New index: '.$new_index, 0);
3176
        }
3177
        $this->index = $new_index;
3178
        if ($this->debug > 2) {
3179
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
3180
        }
3181
        $this->current = $this->ordered_items[$new_index];
3182
        if ($this->debug > 2) {
3183
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
3184
        }
3185
    }
3186
3187
    /**
3188
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
3189
     * class, this might be redefined to allow several behaviours depending on the document type.
3190
     *
3191
     * @param int $id Resource ID
3192
     */
3193
    public function open($id)
3194
    {
3195
        // TODO:
3196
        // set the current resource attribute to this resource
3197
        // switch on element type (redefine in child class?)
3198
        // set status for this item to "opened"
3199
        // start timer
3200
        // initialise score
3201
        $this->index = 0; //or = the last item seen (see $this->last)
3202
    }
3203
3204
    /**
3205
     * Check that all prerequisites are fulfilled. Returns true and an
3206
     * empty string on success, returns false
3207
     * and the prerequisite string on error.
3208
     * This function is based on the rules for aicc_script language as
3209
     * described in the SCORM 1.2 CAM documentation page 108.
3210
     *
3211
     * @param int $itemId Optional item ID. If none given, uses the current open item.
3212
     *
3213
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
3214
     *              string otherwise
3215
     */
3216
    public function prerequisites_match($itemId = null)
3217
    {
3218
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
3219
        if ($allow) {
3220
            if (api_is_allowed_to_edit() ||
3221
                api_is_platform_admin(true) ||
3222
                api_is_drh() ||
3223
                api_is_coach(api_get_session_id(), api_get_course_int_id())
3224
            ) {
3225
                return true;
3226
            }
3227
        }
3228
3229
        $debug = $this->debug;
3230
        if ($debug > 0) {
3231
            error_log('In learnpath::prerequisites_match()');
3232
        }
3233
3234
        if (empty($itemId)) {
3235
            $itemId = $this->current;
3236
        }
3237
3238
        $currentItem = $this->getItem($itemId);
3239
3240
        if ($currentItem) {
3241
            if (2 == $this->type) {
3242
                // Getting prereq from scorm
3243
                $prereq_string = $this->get_scorm_prereq_string($itemId);
3244
            } else {
3245
                $prereq_string = $currentItem->get_prereq_string();
3246
            }
3247
3248
            if (empty($prereq_string)) {
3249
                if ($debug > 0) {
3250
                    error_log('Found prereq_string is empty return true');
3251
                }
3252
3253
                return true;
3254
            }
3255
3256
            // Clean spaces.
3257
            $prereq_string = str_replace(' ', '', $prereq_string);
3258
            if ($debug > 0) {
3259
                error_log('Found prereq_string: '.$prereq_string, 0);
3260
            }
3261
3262
            // Now send to the parse_prereq() function that will check this component's prerequisites.
3263
            $result = $currentItem->parse_prereq(
3264
                $prereq_string,
3265
                $this->items,
3266
                $this->refs_list,
3267
                $this->get_user_id()
3268
            );
3269
3270
            if (false === $result) {
3271
                $this->set_error_msg($currentItem->prereq_alert);
3272
            }
3273
        } else {
3274
            $result = true;
3275
            if ($debug > 1) {
3276
                error_log('$this->items['.$itemId.'] was not an object', 0);
3277
            }
3278
        }
3279
3280
        if ($debug > 1) {
3281
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
3282
        }
3283
3284
        return $result;
3285
    }
3286
3287
    /**
3288
     * Updates learnpath attributes to point to the previous element
3289
     * The last part is similar to set_current_item but processing the other way around.
3290
     */
3291
    public function previous()
3292
    {
3293
        $this->last = $this->get_current_item_id();
3294
        $this->items[$this->last]->save(
3295
            false,
3296
            $this->prerequisites_match($this->last)
3297
        );
3298
        $this->autocomplete_parents($this->last);
3299
        $new_index = $this->get_previous_index();
3300
        $this->index = $new_index;
3301
        $this->current = $this->ordered_items[$new_index];
3302
    }
3303
3304
    /**
3305
     * Publishes a learnpath. This basically means show or hide the learnpath
3306
     * to normal users.
3307
     * Can be used as abstract.
3308
     *
3309
     * @param int $id         Learnpath ID
3310
     * @param int $visibility New visibility (1 = visible/published, 0= invisible/draft)
3311
     *
3312
     * @return bool
3313
     */
3314
    public static function toggleVisibility($id, $visibility = 1)
3315
    {
3316
        $repo = Container::getLpRepository();
3317
        $lp = $repo->find($id);
3318
3319
        if (!$lp) {
3320
            return false;
3321
        }
3322
3323
        $visibility = (int) $visibility;
3324
3325
        if (1 === $visibility) {
3326
            $repo->setVisibilityPublished($lp);
3327
        } else {
3328
            $repo->setVisibilityDraft($lp);
3329
        }
3330
3331
        return true;
3332
    }
3333
3334
    /**
3335
     * Publishes a learnpath category.
3336
     * This basically means show or hide the learnpath category to normal users.
3337
     *
3338
     * @param int $id
3339
     * @param int $visibility
3340
     *
3341
     * @return bool
3342
     */
3343
    public static function toggleCategoryVisibility($id, $visibility = 1)
3344
    {
3345
        $repo = Container::getLpCategoryRepository();
3346
        $resource = $repo->find($id);
3347
3348
        if (!$resource) {
3349
            return false;
3350
        }
3351
3352
        $visibility = (int) $visibility;
3353
3354
        if (1 === $visibility) {
3355
            $repo->setVisibilityPublished($resource);
3356
        } else {
3357
            $repo->setVisibilityDraft($resource);
3358
            self::toggleCategoryPublish($id, 0);
3359
        }
3360
3361
        return false;
3362
    }
3363
3364
    /**
3365
     * Publishes a learnpath. This basically means show or hide the learnpath
3366
     * on the course homepage.
3367
     *
3368
     * @param int    $id            Learnpath id
3369
     * @param string $setVisibility New visibility (v/i - visible/invisible)
3370
     *
3371
     * @return bool
3372
     */
3373
    public static function togglePublish($id, $setVisibility = 'v')
3374
    {
3375
        $addShortcut = false;
3376
        if ('v' === $setVisibility) {
3377
            $addShortcut = true;
3378
        }
3379
        $repo = Container::getLpRepository();
3380
        /** @var CLp|null $lp */
3381
        $lp = $repo->find($id);
3382
        if (null === $lp) {
3383
            return false;
3384
        }
3385
        $repoShortcut = Container::getShortcutRepository();
3386
        $courseEntity = api_get_course_entity();
3387
3388
        if ($addShortcut) {
3389
            $repoShortcut->addShortCut($lp, $courseEntity, $courseEntity, api_get_session_entity());
3390
        } else {
3391
            $repoShortcut->removeShortCut($lp);
3392
        }
3393
3394
        return true;
3395
    }
3396
3397
    /**
3398
     * Show or hide the learnpath category on the course homepage.
3399
     *
3400
     * @param int $id
3401
     * @param int $setVisibility
3402
     *
3403
     * @return bool
3404
     */
3405
    public static function toggleCategoryPublish($id, $setVisibility = 1)
3406
    {
3407
        $setVisibility = (int) $setVisibility;
3408
        $addShortcut = false;
3409
        if (1 === $setVisibility) {
3410
            $addShortcut = true;
3411
        }
3412
3413
        $repo = Container::getLpCategoryRepository();
3414
        /** @var CLpCategory|null $lp */
3415
        $category = $repo->find($id);
3416
3417
        if (null === $category) {
3418
            return false;
3419
        }
3420
3421
        $repoShortcut = Container::getShortcutRepository();
3422
        if ($addShortcut) {
3423
            $courseEntity = api_get_course_entity(api_get_course_int_id());
3424
            $repoShortcut->addShortCut($category, $courseEntity, $courseEntity, api_get_session_entity());
3425
        } else {
3426
            $repoShortcut->removeShortCut($category);
3427
        }
3428
3429
        return true;
3430
    }
3431
3432
    /**
3433
     * Check if the learnpath category is visible for a user.
3434
     *
3435
     * @return bool
3436
     */
3437
    public static function categoryIsVisibleForStudent(
3438
        CLpCategory $category,
3439
        User $user,
3440
        Course $course,
3441
        Session $session = null
3442
    ) {
3443
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
3444
3445
        if ($isAllowedToEdit) {
3446
            return true;
3447
        }
3448
3449
        $categoryVisibility = $category->isVisible($course, $session);
3450
3451
        if (!$categoryVisibility) {
3452
            return false;
3453
        }
3454
3455
        $subscriptionSettings = self::getSubscriptionSettings();
3456
3457
        if (false === $subscriptionSettings['allow_add_users_to_lp_category']) {
3458
            return true;
3459
        }
3460
3461
        $noUserSubscribed = false;
3462
        $noGroupSubscribed = true;
3463
        $users = $category->getUsers();
3464
        if (empty($users) || !$users->count()) {
3465
            $noUserSubscribed = true;
3466
        } elseif ($category->hasUserAdded($user)) {
3467
            return true;
3468
        }
3469
3470
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
3471
        $em = Database::getManager();
3472
3473
        /** @var ItemPropertyRepository $itemRepo */
3474
        $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
3475
3476
        /** @var CourseRepository $courseRepo */
3477
        $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
3478
        $session = null;
3479
        if (!empty($sessionId)) {
3480
            $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
3481
        }
3482
3483
        $course = $courseRepo->find($courseId);
3484
3485
        if (0 != $courseId) {
3486
            // Subscribed groups to a LP
3487
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
3488
                    TOOL_LEARNPATH_CATEGORY,
3489
                    $category->getId(),
3490
                    $course,
3491
                    $session
3492
                );
3493
        }
3494
3495
        if (!empty($subscribedGroupsInLp)) {
3496
            $noGroupSubscribed = false;
3497
            if (!empty($groups)) {
3498
                $groups = array_column($groups, 'iid');
3499
                /** @var CItemProperty $item */
3500
                foreach ($subscribedGroupsInLp as $item) {
3501
                    if ($item->getGroup() &&
3502
                        in_array($item->getGroup()->getId(), $groups)
3503
                    ) {
3504
                        return true;
3505
                    }
3506
                }
3507
            }
3508
        }
3509
        $response = $noGroupSubscribed && $noUserSubscribed;
3510
3511
        return $response;
3512
    }
3513
3514
    /**
3515
     * Check if a learnpath category is published as course tool.
3516
     *
3517
     * @param int $courseId
3518
     *
3519
     * @return bool
3520
     */
3521
    public static function categoryIsPublished(CLpCategory $category, $courseId)
3522
    {
3523
        return false;
3524
        $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...
3525
        $em = Database::getManager();
3526
3527
        $tools = $em
3528
            ->createQuery("
3529
                SELECT t FROM ChamiloCourseBundle:CTool t
3530
                WHERE t.course = :course AND
3531
                    t.name = :name AND
3532
                    t.image LIKE 'lp_category.%' AND
3533
                    t.link LIKE :link
3534
            ")
3535
            ->setParameters([
3536
                'course' => $courseId,
3537
                'name' => strip_tags($category->getName()),
3538
                'link' => "$link%",
3539
            ])
3540
            ->getResult();
3541
3542
        /** @var CTool $tool */
3543
        $tool = current($tools);
3544
3545
        return $tool ? $tool->getVisibility() : false;
3546
    }
3547
3548
    /**
3549
     * Restart the whole learnpath. Return the URL of the first element.
3550
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
3551
     * To use a similar method  statically, use the create_new_attempt() method.
3552
     *
3553
     * @return bool
3554
     */
3555
    public function restart()
3556
    {
3557
        if ($this->debug > 0) {
3558
            error_log('In learnpath::restart()', 0);
3559
        }
3560
        // TODO
3561
        // Call autosave method to save the current progress.
3562
        //$this->index = 0;
3563
        if (api_is_invitee()) {
3564
            return false;
3565
        }
3566
        $session_id = api_get_session_id();
3567
        $course_id = api_get_course_int_id();
3568
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3569
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
3570
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
3571
        if ($this->debug > 2) {
3572
            error_log('Inserting new lp_view for restart: '.$sql, 0);
3573
        }
3574
        Database::query($sql);
3575
        $view_id = Database::insert_id();
3576
3577
        if ($view_id) {
3578
            $this->lp_view_id = $view_id;
3579
            $this->attempt = $this->attempt + 1;
3580
        } else {
3581
            $this->error = 'Could not insert into item_view table...';
3582
3583
            return false;
3584
        }
3585
        $this->autocomplete_parents($this->current);
3586
        foreach ($this->items as $index => $dummy) {
3587
            $this->items[$index]->restart();
3588
            $this->items[$index]->set_lp_view($this->lp_view_id);
3589
        }
3590
        $this->first();
3591
3592
        return true;
3593
    }
3594
3595
    /**
3596
     * Saves the current item.
3597
     *
3598
     * @return bool
3599
     */
3600
    public function save_current()
3601
    {
3602
        $debug = $this->debug;
3603
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
3604
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
3605
        if ($debug) {
3606
            error_log('save_current() saving item '.$this->current, 0);
3607
            error_log(''.print_r($this->items, true), 0);
3608
        }
3609
        if (isset($this->items[$this->current]) &&
3610
            is_object($this->items[$this->current])
3611
        ) {
3612
            if ($debug) {
3613
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
3614
            }
3615
3616
            $res = $this->items[$this->current]->save(
3617
                false,
3618
                $this->prerequisites_match($this->current)
3619
            );
3620
            $this->autocomplete_parents($this->current);
3621
            $status = $this->items[$this->current]->get_status();
3622
            $this->update_queue[$this->current] = $status;
3623
3624
            if ($debug) {
3625
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
3626
            }
3627
3628
            return $res;
3629
        }
3630
3631
        return false;
3632
    }
3633
3634
    /**
3635
     * Saves the given item.
3636
     *
3637
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
3638
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
3639
     *
3640
     * @return bool
3641
     */
3642
    public function save_item($item_id = null, $from_outside = true)
3643
    {
3644
        $debug = $this->debug;
3645
        if ($debug) {
3646
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
3647
        }
3648
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
3649
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
3650
        if (empty($item_id)) {
3651
            $item_id = (int) $_REQUEST['id'];
3652
        }
3653
3654
        if (empty($item_id)) {
3655
            $item_id = $this->get_current_item_id();
3656
        }
3657
        if (isset($this->items[$item_id]) &&
3658
            is_object($this->items[$item_id])
3659
        ) {
3660
            if ($debug) {
3661
                error_log('Object exists');
3662
            }
3663
3664
            // Saving the item.
3665
            $res = $this->items[$item_id]->save(
3666
                $from_outside,
3667
                $this->prerequisites_match($item_id)
3668
            );
3669
3670
            if ($debug) {
3671
                error_log('update_queue before:');
3672
                error_log(print_r($this->update_queue, 1));
3673
            }
3674
            $this->autocomplete_parents($item_id);
3675
3676
            $status = $this->items[$item_id]->get_status();
3677
            $this->update_queue[$item_id] = $status;
3678
3679
            if ($debug) {
3680
                error_log('get_status(): '.$status);
3681
                error_log('update_queue after:');
3682
                error_log(print_r($this->update_queue, 1));
3683
            }
3684
3685
            return $res;
3686
        }
3687
3688
        return false;
3689
    }
3690
3691
    /**
3692
     * Saves the last item seen's ID only in case.
3693
     */
3694
    public function save_last()
3695
    {
3696
        $course_id = api_get_course_int_id();
3697
        $debug = $this->debug;
3698
        if ($debug) {
3699
            error_log('In learnpath::save_last()', 0);
3700
        }
3701
        $session_condition = api_get_session_condition(
3702
            api_get_session_id(),
3703
            true,
3704
            false
3705
        );
3706
        $table = Database::get_course_table(TABLE_LP_VIEW);
3707
3708
        $userId = $this->get_user_id();
3709
        if (empty($userId)) {
3710
            $userId = api_get_user_id();
3711
            if ($debug) {
3712
                error_log('$this->get_user_id() was empty, used api_get_user_id() instead in '.__FILE__.' line '.__LINE__);
3713
            }
3714
        }
3715
        if (isset($this->current) && !api_is_invitee()) {
3716
            if ($debug) {
3717
                error_log('Saving current item ('.$this->current.') for later review', 0);
3718
            }
3719
            $sql = "UPDATE $table SET
3720
                        last_item = ".$this->get_current_item_id()."
3721
                    WHERE
3722
                        c_id = $course_id AND
3723
                        lp_id = ".$this->get_id()." AND
3724
                        user_id = ".$userId." ".$session_condition;
3725
3726
            if ($debug) {
3727
                error_log('Saving last item seen : '.$sql, 0);
3728
            }
3729
            Database::query($sql);
3730
        }
3731
3732
        if (!api_is_invitee()) {
3733
            // Save progress.
3734
            [$progress] = $this->get_progress_bar_text('%');
3735
            $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
3736
            $scoreAsProgress = $this->getUseScoreAsProgress();
3737
            if ($scoreAsProgress && $scoreAsProgressSetting && (null === $score || empty($score) || -1 == $score)) {
3738
                if ($debug) {
3739
                    error_log("Return false: Dont save score: $score");
3740
                    error_log("progress: $progress");
3741
                }
3742
3743
                return false;
3744
            }
3745
3746
            if ($scoreAsProgress && $scoreAsProgressSetting) {
3747
                $storedProgress = self::getProgress(
3748
                    $this->get_id(),
3749
                    $userId,
3750
                    $course_id,
3751
                    $this->get_lp_session_id()
3752
                );
3753
3754
                // Check if the stored progress is higher than the new value
3755
                if ($storedProgress >= $progress) {
3756
                    if ($debug) {
3757
                        error_log("Return false: New progress value is lower than stored value - Current value: $storedProgress - New value: $progress [lp ".$this->get_id()." - user ".$userId."]");
3758
                    }
3759
3760
                    return false;
3761
                }
3762
            }
3763
            if ($progress >= 0 && $progress <= 100) {
3764
                $progress = (int) $progress;
3765
                $sql = "UPDATE $table SET
3766
                            progress = $progress
3767
                        WHERE
3768
                            c_id = $course_id AND
3769
                            lp_id = ".$this->get_id()." AND
3770
                            user_id = ".$userId." ".$session_condition;
3771
                // Ignore errors as some tables might not have the progress field just yet.
3772
                Database::query($sql);
3773
                $this->progress_db = $progress;
3774
            }
3775
        }
3776
    }
3777
3778
    /**
3779
     * Sets the current item ID (checks if valid and authorized first).
3780
     *
3781
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
3782
     */
3783
    public function set_current_item($item_id = null)
3784
    {
3785
        $debug = $this->debug;
3786
        if ($debug) {
3787
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
3788
        }
3789
        if (empty($item_id)) {
3790
            if ($debug) {
3791
                error_log('No new current item given, ignore...', 0);
3792
            }
3793
            // Do nothing.
3794
        } else {
3795
            if ($debug) {
3796
                error_log('New current item given is '.$item_id.'...', 0);
3797
            }
3798
            if (is_numeric($item_id)) {
3799
                $item_id = (int) $item_id;
3800
                // TODO: Check in database here.
3801
                $this->last = $this->current;
3802
                $this->current = $item_id;
3803
                // TODO: Update $this->index as well.
3804
                foreach ($this->ordered_items as $index => $item) {
3805
                    if ($item == $this->current) {
3806
                        $this->index = $index;
3807
                        break;
3808
                    }
3809
                }
3810
                if ($debug) {
3811
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
3812
                }
3813
            } else {
3814
                if ($debug) {
3815
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
3816
                }
3817
            }
3818
        }
3819
    }
3820
3821
    /**
3822
     * Set index specified prefix terms for all items in this path.
3823
     *
3824
     * @param string $terms_string Comma-separated list of terms
3825
     * @param string $prefix       Xapian term prefix
3826
     *
3827
     * @return bool False on error, true otherwise
3828
     */
3829
    public function set_terms_by_prefix($terms_string, $prefix)
3830
    {
3831
        $course_id = api_get_course_int_id();
3832
        if ('true' !== api_get_setting('search_enabled')) {
3833
            return false;
3834
        }
3835
3836
        if (!extension_loaded('xapian')) {
3837
            return false;
3838
        }
3839
3840
        $terms_string = trim($terms_string);
3841
        $terms = explode(',', $terms_string);
3842
        array_walk($terms, 'trim_value');
3843
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
3844
3845
        // Don't do anything if no change, verify only at DB, not the search engine.
3846
        if ((0 == count(array_diff($terms, $stored_terms))) && (0 == count(array_diff($stored_terms, $terms)))) {
3847
            return false;
3848
        }
3849
3850
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
3851
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
3852
3853
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
3854
        // TODO: Make query secure agains XSS : use member attr instead of post var.
3855
        $lp_id = (int) $_POST['lp_id'];
3856
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
3857
        $result = Database::query($sql);
3858
        $di = new ChamiloIndexer();
3859
3860
        while ($lp_item = Database::fetch_array($result)) {
3861
            // Get search_did.
3862
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
3863
            $sql = 'SELECT * FROM %s
3864
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
3865
                    LIMIT 1';
3866
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
3867
3868
            //echo $sql; echo '<br>';
3869
            $res = Database::query($sql);
3870
            if (Database::num_rows($res) > 0) {
3871
                $se_ref = Database::fetch_array($res);
3872
                // Compare terms.
3873
                $doc = $di->get_document($se_ref['search_did']);
3874
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
3875
                $xterms = [];
3876
                foreach ($xapian_terms as $xapian_term) {
3877
                    $xterms[] = substr($xapian_term['name'], 1);
3878
                }
3879
3880
                $dterms = $terms;
3881
                $missing_terms = array_diff($dterms, $xterms);
3882
                $deprecated_terms = array_diff($xterms, $dterms);
3883
3884
                // Save it to search engine.
3885
                foreach ($missing_terms as $term) {
3886
                    $doc->add_term($prefix.$term, 1);
3887
                }
3888
                foreach ($deprecated_terms as $term) {
3889
                    $doc->remove_term($prefix.$term);
3890
                }
3891
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
3892
                $di->getDb()->flush();
3893
            }
3894
        }
3895
3896
        return true;
3897
    }
3898
3899
    /**
3900
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
3901
     *
3902
     * @param int $id DB ID of the item
3903
     */
3904
    public function set_previous_item($id)
3905
    {
3906
        if ($this->debug > 0) {
3907
            error_log('In learnpath::set_previous_item()', 0);
3908
        }
3909
        $this->last = $id;
3910
    }
3911
3912
    /**
3913
     * Sets and saves the expired_on date.
3914
     *
3915
     * @return bool Returns true if author's name is not empty
3916
     */
3917
    public function set_modified_on()
3918
    {
3919
        $this->modified_on = api_get_utc_datetime();
3920
        $table = Database::get_course_table(TABLE_LP_MAIN);
3921
        $lp_id = $this->get_id();
3922
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
3923
                WHERE iid = $lp_id";
3924
        Database::query($sql);
3925
3926
        return true;
3927
    }
3928
3929
    /**
3930
     * Sets the object's error message.
3931
     *
3932
     * @param string $error Error message. If empty, reinits the error string
3933
     */
3934
    public function set_error_msg($error = '')
3935
    {
3936
        if ($this->debug > 0) {
3937
            error_log('In learnpath::set_error_msg()', 0);
3938
        }
3939
        if (empty($error)) {
3940
            $this->error = '';
3941
        } else {
3942
            $this->error .= $error;
3943
        }
3944
    }
3945
3946
    /**
3947
     * Launches the current item if not 'sco'
3948
     * (starts timer and make sure there is a record ready in the DB).
3949
     *
3950
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
3951
     *
3952
     * @return bool
3953
     */
3954
    public function start_current_item($allow_new_attempt = false)
3955
    {
3956
        $debug = $this->debug;
3957
        if ($debug) {
3958
            error_log('In learnpath::start_current_item()');
3959
            error_log('current: '.$this->current);
3960
        }
3961
        if (0 != $this->current && isset($this->items[$this->current]) &&
3962
            is_object($this->items[$this->current])
3963
        ) {
3964
            $type = $this->get_type();
3965
            $item_type = $this->items[$this->current]->get_type();
3966
            if ($debug) {
3967
                error_log('item type: '.$item_type);
3968
                error_log('lp type: '.$type);
3969
            }
3970
            if ((2 == $type && 'sco' !== $item_type) ||
3971
                (3 == $type && 'au' !== $item_type) ||
3972
                (1 == $type && TOOL_QUIZ != $item_type && TOOL_HOTPOTATOES != $item_type)
3973
            ) {
3974
                $this->items[$this->current]->open($allow_new_attempt);
3975
                $this->autocomplete_parents($this->current);
3976
                $prereq_check = $this->prerequisites_match($this->current);
3977
                if ($debug) {
3978
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
3979
                }
3980
                $this->items[$this->current]->save(false, $prereq_check);
3981
            }
3982
            // If sco, then it is supposed to have been updated by some other call.
3983
            if ('sco' === $item_type) {
3984
                $this->items[$this->current]->restart();
3985
            }
3986
        }
3987
        if ($debug) {
3988
            error_log('lp_view_session_id');
3989
            error_log($this->lp_view_session_id);
3990
            error_log('api session id');
3991
            error_log(api_get_session_id());
3992
            error_log('End of learnpath::start_current_item()');
3993
        }
3994
3995
        return true;
3996
    }
3997
3998
    /**
3999
     * Stops the processing and counters for the old item (as held in $this->last).
4000
     *
4001
     * @return bool True/False
4002
     */
4003
    public function stop_previous_item()
4004
    {
4005
        $debug = $this->debug;
4006
        if ($debug) {
4007
            error_log('In learnpath::stop_previous_item()', 0);
4008
        }
4009
4010
        if (0 != $this->last && $this->last != $this->current &&
4011
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
4012
        ) {
4013
            if ($debug) {
4014
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
4015
            }
4016
            switch ($this->get_type()) {
4017
                case '3':
4018
                    if ('au' != $this->items[$this->last]->get_type()) {
4019
                        if ($debug) {
4020
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
4021
                        }
4022
                        $this->items[$this->last]->close();
4023
                    } else {
4024
                        if ($debug) {
4025
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
4026
                        }
4027
                    }
4028
                    break;
4029
                case '2':
4030
                    if ('sco' != $this->items[$this->last]->get_type()) {
4031
                        if ($debug) {
4032
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
4033
                        }
4034
                        $this->items[$this->last]->close();
4035
                    } else {
4036
                        if ($debug) {
4037
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
4038
                        }
4039
                    }
4040
                    break;
4041
                case '1':
4042
                default:
4043
                    if ($debug) {
4044
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
4045
                    }
4046
                    $this->items[$this->last]->close();
4047
                    break;
4048
            }
4049
        } else {
4050
            if ($debug) {
4051
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
4052
            }
4053
4054
            return false;
4055
        }
4056
4057
        return true;
4058
    }
4059
4060
    /**
4061
     * Updates the default view mode from fullscreen to embedded and inversely.
4062
     *
4063
     * @return string The current default view mode ('fullscreen' or 'embedded')
4064
     */
4065
    public function update_default_view_mode()
4066
    {
4067
        $table = Database::get_course_table(TABLE_LP_MAIN);
4068
        $sql = "SELECT * FROM $table
4069
                WHERE iid = ".$this->get_id();
4070
        $res = Database::query($sql);
4071
        if (Database::num_rows($res) > 0) {
4072
            $row = Database::fetch_array($res);
4073
            $default_view_mode = $row['default_view_mod'];
4074
            $view_mode = $default_view_mode;
4075
            switch ($default_view_mode) {
4076
                case 'fullscreen': // default with popup
4077
                    $view_mode = 'embedded';
4078
                    break;
4079
                case 'embedded': // default view with left menu
4080
                    $view_mode = 'embedframe';
4081
                    break;
4082
                case 'embedframe': //folded menu
4083
                    $view_mode = 'impress';
4084
                    break;
4085
                case 'impress':
4086
                    $view_mode = 'fullscreen';
4087
                    break;
4088
            }
4089
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
4090
                    WHERE iid = ".$this->get_id();
4091
            Database::query($sql);
4092
            $this->mode = $view_mode;
4093
4094
            return $view_mode;
4095
        }
4096
4097
        return -1;
4098
    }
4099
4100
    /**
4101
     * Updates the default behaviour about auto-commiting SCORM updates.
4102
     *
4103
     * @return bool True if auto-commit has been set to 'on', false otherwise
4104
     */
4105
    public function update_default_scorm_commit()
4106
    {
4107
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4108
        $sql = "SELECT * FROM $lp_table
4109
                WHERE iid = ".$this->get_id();
4110
        $res = Database::query($sql);
4111
        if (Database::num_rows($res) > 0) {
4112
            $row = Database::fetch_array($res);
4113
            $force = $row['force_commit'];
4114
            if (1 == $force) {
4115
                $force = 0;
4116
                $force_return = false;
4117
            } elseif (0 == $force) {
4118
                $force = 1;
4119
                $force_return = true;
4120
            }
4121
            $sql = "UPDATE $lp_table SET force_commit = $force
4122
                    WHERE iid = ".$this->get_id();
4123
            Database::query($sql);
4124
            $this->force_commit = $force_return;
4125
4126
            return $force_return;
4127
        }
4128
4129
        return -1;
4130
    }
4131
4132
    /**
4133
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
4134
     *
4135
     * @return bool True on success, false on failure
4136
     */
4137
    public function update_display_order()
4138
    {
4139
        return;
4140
        $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...
4141
        $table = Database::get_course_table(TABLE_LP_MAIN);
4142
        $sql = "SELECT * FROM $table
4143
                WHERE c_id = $course_id
4144
                ORDER BY display_order";
4145
        $res = Database::query($sql);
4146
        if (false === $res) {
4147
            return false;
4148
        }
4149
4150
        $num = Database::num_rows($res);
4151
        // First check the order is correct, globally (might be wrong because
4152
        // of versions < 1.8.4).
4153
        if ($num > 0) {
4154
            $i = 1;
4155
            while ($row = Database::fetch_array($res)) {
4156
                if ($row['display_order'] != $i) {
4157
                    // If we find a gap in the order, we need to fix it.
4158
                    $sql = "UPDATE $table SET display_order = $i
4159
                            WHERE iid = ".$row['iid'];
4160
                    Database::query($sql);
4161
                }
4162
                $i++;
4163
            }
4164
        }
4165
4166
        return true;
4167
    }
4168
4169
    /**
4170
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
4171
     *
4172
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
4173
     */
4174
    public function update_reinit()
4175
    {
4176
        $force = $this->entity->getPreventReinit();
4177
        if (1 == $force) {
4178
            $force = 0;
4179
        } elseif (0 == $force) {
4180
            $force = 1;
4181
        }
4182
4183
        $table = Database::get_course_table(TABLE_LP_MAIN);
4184
        $sql = "UPDATE $table SET prevent_reinit = $force
4185
                WHERE iid = ".$this->get_id();
4186
        Database::query($sql);
4187
        $this->prevent_reinit = $force;
4188
4189
        return $force;
4190
    }
4191
4192
    /**
4193
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
4194
     *
4195
     * @return string 'single', 'multi' or 'seriousgame'
4196
     *
4197
     * @author ndiechburg <[email protected]>
4198
     */
4199
    public function get_attempt_mode()
4200
    {
4201
        //Set default value for seriousgame_mode
4202
        if (!isset($this->seriousgame_mode)) {
4203
            $this->seriousgame_mode = 0;
4204
        }
4205
        // Set default value for prevent_reinit
4206
        if (!isset($this->prevent_reinit)) {
4207
            $this->prevent_reinit = 1;
4208
        }
4209
        if (1 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
4210
            return 'seriousgame';
4211
        }
4212
        if (0 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
4213
            return 'single';
4214
        }
4215
        if (0 == $this->seriousgame_mode && 0 == $this->prevent_reinit) {
4216
            return 'multiple';
4217
        }
4218
4219
        return 'single';
4220
    }
4221
4222
    /**
4223
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
4224
     *
4225
     * @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...
4226
     *
4227
     * @return bool
4228
     *
4229
     * @author ndiechburg <[email protected]>
4230
     */
4231
    public function set_attempt_mode($mode)
4232
    {
4233
        switch ($mode) {
4234
            case 'seriousgame':
4235
                $sg_mode = 1;
4236
                $prevent_reinit = 1;
4237
                break;
4238
            case 'single':
4239
                $sg_mode = 0;
4240
                $prevent_reinit = 1;
4241
                break;
4242
            case 'multiple':
4243
                $sg_mode = 0;
4244
                $prevent_reinit = 0;
4245
                break;
4246
            default:
4247
                $sg_mode = 0;
4248
                $prevent_reinit = 0;
4249
                break;
4250
        }
4251
        $this->prevent_reinit = $prevent_reinit;
4252
        $this->seriousgame_mode = $sg_mode;
4253
        $table = Database::get_course_table(TABLE_LP_MAIN);
4254
        $sql = "UPDATE $table SET
4255
                prevent_reinit = $prevent_reinit ,
4256
                seriousgame_mode = $sg_mode
4257
                WHERE iid = ".$this->get_id();
4258
        $res = Database::query($sql);
4259
        if ($res) {
0 ignored issues
show
introduced by
$res is of type Doctrine\DBAL\Driver\Statement, thus it always evaluated to true.
Loading history...
4260
            return true;
4261
        } else {
4262
            return false;
4263
        }
4264
    }
4265
4266
    /**
4267
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
4268
     *
4269
     * @author ndiechburg <[email protected]>
4270
     */
4271
    public function switch_attempt_mode()
4272
    {
4273
        $mode = $this->get_attempt_mode();
4274
        switch ($mode) {
4275
            case 'single':
4276
                $next_mode = 'multiple';
4277
                break;
4278
            case 'multiple':
4279
                $next_mode = 'seriousgame';
4280
                break;
4281
            case 'seriousgame':
4282
            default:
4283
                $next_mode = 'single';
4284
                break;
4285
        }
4286
        $this->set_attempt_mode($next_mode);
4287
    }
4288
4289
    /**
4290
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
4291
     * but possibility to do again a completed item.
4292
     *
4293
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
4294
     *
4295
     * @author ndiechburg <[email protected]>
4296
     */
4297
    public function set_seriousgame_mode()
4298
    {
4299
        $table = Database::get_course_table(TABLE_LP_MAIN);
4300
        $force = $this->entity->getSeriousgameMode();
4301
        if (1 == $force) {
4302
            $force = 0;
4303
        } elseif (0 == $force) {
4304
            $force = 1;
4305
        }
4306
        $sql = "UPDATE $table SET seriousgame_mode = $force
4307
                WHERE iid = ".$this->get_id();
4308
        Database::query($sql);
4309
        $this->seriousgame_mode = $force;
4310
4311
        return $force;
4312
    }
4313
4314
    /**
4315
     * Updates the "scorm_debug" value that shows or hide the debug window.
4316
     *
4317
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
4318
     */
4319
    public function update_scorm_debug()
4320
    {
4321
        $table = Database::get_course_table(TABLE_LP_MAIN);
4322
        $force = $this->entity->getDebug();
4323
        if (1 == $force) {
4324
            $force = 0;
4325
        } elseif (0 == $force) {
4326
            $force = 1;
4327
        }
4328
        $sql = "UPDATE $table SET debug = $force
4329
                WHERE iid = ".$this->get_id();
4330
        Database::query($sql);
4331
        $this->scorm_debug = $force;
4332
4333
        return $force;
4334
    }
4335
4336
    /**
4337
     * Function that creates a html list of learning path items so that we can add audio files to them.
4338
     *
4339
     * @author Kevin Van Den Haute
4340
     *
4341
     * @return string
4342
     */
4343
    public function overview()
4344
    {
4345
        $return = '';
4346
        $update_audio = $_GET['updateaudio'] ?? null;
4347
4348
        // we need to start a form when we want to update all the mp3 files
4349
        if ('true' == $update_audio) {
4350
            $return .= '<form action="'.api_get_self().'?'.api_get_cidreq().'&updateaudio='.Security::remove_XSS(
4351
                    $_GET['updateaudio']
4352
                ).'&action='.Security::remove_XSS(
4353
                    $_GET['action']
4354
                ).'&lp_id='.$_SESSION['oLP']->lp_id.'" method="post" enctype="multipart/form-data" name="updatemp3" id="updatemp3">';
4355
        }
4356
        $return .= '<div id="message"></div>';
4357
        if (0 == count($this->items)) {
4358
            $return .= Display::return_message(
4359
                get_lang(
4360
                    'You should add some items to your learning path, otherwise you won\'t be able to attach audio files to them'
4361
                ),
4362
                'normal'
4363
            );
4364
        } else {
4365
            $return_audio = '<table class="table table-hover table-striped data_table">';
4366
            $return_audio .= '<tr>';
4367
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
4368
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
4369
            $return_audio .= '</tr>';
4370
4371
            if ('true' != $update_audio) {
4372
                /*$return .= '<div class="col-md-12">';
4373
                $return .= self::return_new_tree($update_audio);
4374
                $return .= '</div>';*/
4375
                $return .= Display::div(
4376
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
4377
                    ['style' => 'float:left; margin-top:15px;width:100%']
4378
                );
4379
            } else {
4380
                //$return_audio .= self::return_new_tree($update_audio);
4381
                $return .= $return_audio.'</table>';
4382
            }
4383
4384
            // We need to close the form when we are updating the mp3 files.
4385
            if ('true' == $update_audio) {
4386
                $return .= '<div class="footer-audio">';
4387
                $return .= Display::button(
4388
                    'save_audio',
4389
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('Save audio and organization'),
4390
                    ['class' => 'btn btn-primary', 'type' => 'submit']
4391
                );
4392
                $return .= '</div>';
4393
            }
4394
        }
4395
4396
        // We need to close the form when we are updating the mp3 files.
4397
        if ('true' === $update_audio && isset($this->arrMenu) && 0 != count($this->arrMenu)) {
4398
            $return .= '</form>';
4399
        }
4400
4401
        return $return;
4402
    }
4403
4404
    public function showBuildSideBar($updateAudio = false, $dropElementHere = false, $type = null)
4405
    {
4406
        $sureToDelete = trim(get_lang('Are you sure to delete?'));
4407
        $ajax_url = api_get_path(WEB_AJAX_PATH).'lp.ajax.php?lp_id='.$this->get_id().'&'.api_get_cidreq();
4408
4409
        $content = '
4410
        <script>
4411
            /*
4412
            Script to manipulate Learning Path items with Drag and drop
4413
             */
4414
            $(function() {
4415
                function refreshTree() {
4416
                    var params = "&a=get_lp_item_tree";
4417
                    $.get(
4418
                        "'.$ajax_url.'",
4419
                        params,
4420
                        function(result) {
4421
                            serialized = [];
4422
                            $("#lp_item_list").html(result);
4423
                            nestedSortable();
4424
                        }
4425
                    );
4426
                }
4427
4428
                const nestedQuery = ".nested-sortable";
4429
                const identifier = "id";
4430
                const root = document.getElementById("lp_item_list");
4431
4432
                var serialized = [];
4433
                function serialize(sortable) {
4434
                  var children = [].slice.call(sortable.children);
4435
                  for (var i in children) {
4436
                    var nested = children[i].querySelector(nestedQuery);
4437
                    var parentId = $(children[i]).parent().parent().attr("id");
4438
                    var id = children[i].dataset[identifier];
4439
                    if (typeof id === "undefined") {
4440
                        return;
4441
                    }
4442
                    serialized.push({
4443
                      id: children[i].dataset[identifier],
4444
                      parent_id: parentId
4445
                    });
4446
4447
                    if (nested) {
4448
                        serialize(nested);
4449
                    }
4450
                  }
4451
4452
                  return serialized;
4453
                }
4454
4455
                function nestedSortable() {
4456
                    let left = document.getElementsByClassName("nested-sortable");
4457
                    Array.prototype.forEach.call(left, function(resource) {
4458
                        Sortable.create(resource, {
4459
                            group: "nested",
4460
                            put: ["nested-sortable", ".lp_resource", ".nested-source"],
4461
                            animation: 150,
4462
                            //fallbackOnBody: true,
4463
                            swapThreshold: 0.65,
4464
                            dataIdAttr: "data-id",
4465
                            store: {
4466
                                set: function (sortable) {
4467
                                    var order = sortable.toArray();
4468
                                    console.log(order);
4469
                                }
4470
                            },
4471
                            onEnd: function(evt) {
4472
                                console.log("onEnd");
4473
                                let list = serialize(root);
4474
                                let order = "&a=update_lp_item_order&new_order=" + JSON.stringify(list);
4475
                                $.get(
4476
                                    "'.$ajax_url.'",
4477
                                    order,
4478
                                    function(reponse) {
4479
                                        $("#message").html(reponse);
4480
                                        refreshTree();
4481
                                    }
4482
                                );
4483
                            },
4484
                        });
4485
                    });
4486
                }
4487
4488
                nestedSortable();
4489
4490
                let resources = document.getElementsByClassName("lp_resource");
4491
                Array.prototype.forEach.call(resources, function(resource) {
4492
                    Sortable.create(resource, {
4493
                        group: "nested",
4494
                        put: ["nested-sortable"],
4495
                        filter: ".disable_drag",
4496
                        animation: 150,
4497
                        fallbackOnBody: true,
4498
                        swapThreshold: 0.65,
4499
                        dataIdAttr: "data-id",
4500
                        onRemove: function(evt) {
4501
                            console.log("onRemove");
4502
                            var itemEl = evt.item;
4503
                            var newIndex = evt.newIndex;
4504
                            var id = $(itemEl).attr("id");
4505
                            var parent_id = $(itemEl).parent().parent().attr("id");
4506
                            var type =  $(itemEl).find(".link_with_id").attr("data_type");
4507
                            var title = $(itemEl).find(".link_with_id").text();
4508
4509
                            let previousId = 0;
4510
                            if (0 !== newIndex) {
4511
                                previousId = $(itemEl).prev().attr("id");
4512
                            }
4513
                            var params = {
4514
                                "a": "add_lp_item",
4515
                                "id": id,
4516
                                "parent_id": parent_id,
4517
                                "previous_id": previousId,
4518
                                "type": type,
4519
                                "title" : title
4520
                            };
4521
                            console.log(params);
4522
                            $.ajax({
4523
                                type: "GET",
4524
                                url: "'.$ajax_url.'",
4525
                                data: params,
4526
                                success: function(itemId) {
4527
                                    $(itemEl).attr("id", itemId);
4528
                                    $(itemEl).attr("data-id", itemId);
4529
                                    let list = serialize(root);
4530
                                    let listInString = JSON.stringify(list);
4531
                                    if (typeof listInString === "undefined") {
4532
                                        listInString = "";
4533
                                    }
4534
                                    let order = "&a=update_lp_item_order&new_order=" + listInString;
4535
                                    $.get(
4536
                                        "'.$ajax_url.'",
4537
                                        order,
4538
                                        function(reponse) {
4539
                                            $("#message").html(reponse);
4540
                                            refreshTree();
4541
                                        }
4542
                                    );
4543
                                }
4544
                            });
4545
                        },
4546
                    });
4547
                });
4548
            });
4549
        </script>';
4550
4551
        $content .= "
4552
        <script>
4553
            function confirmation(name) {
4554
                if (confirm('$sureToDelete ' + name)) {
4555
                    return true;
4556
                } else {
4557
                    return false;
4558
                }
4559
            }
4560
            function refreshTree() {
4561
                var params = '&a=get_lp_item_tree';
4562
                $.get(
4563
                    '".$ajax_url."',
4564
                    params,
4565
                    function(result) {
4566
                        $('#lp_item_list').html(result);
4567
                    }
4568
                );
4569
            }
4570
4571
            $(function () {
4572
                //$('.scrollbar-inner').scrollbar();
4573
                /*$('#subtab').on('click', 'a:first', function() {
4574
                    window.location.reload();
4575
                });
4576
                $('#subtab ').on('click', 'a:first', function () {
4577
                    window.location.reload();
4578
                });*/
4579
                
4580
                expandColumnToggle('#hide_bar_template', {
4581
                    selector: '#lp_sidebar'
4582
                }, {
4583
                    selector: '#doc_form'
4584
                });
4585
4586
                $('.lp-btn-associate-forum').on('click', function (e) {
4587
                    var associate = confirm('".get_lang('ConfirmAssociateForumToLPItem')."');
4588
                    if (!associate) {
4589
                        e.preventDefault();
4590
                    }
4591
                });
4592
4593
                $('.lp-btn-dissociate-forum').on('click', function (e) {
4594
                    var dissociate = confirm('".get_lang('ConfirmDissociateForumToLPItem')."');
4595
                    if (!dissociate) {
4596
                        e.preventDefault();
4597
                    }
4598
                });
4599
4600
                // hide the current template list for new documment until it tab clicked
4601
                $('#frmModel').hide();
4602
            });
4603
4604
            // document template for new document tab handler
4605
            /*$(document).on('shown.bs.tab', 'a[data-toggle=\"tab\"]', function (e) {
4606
                var id = e.target.id;
4607
                if (id == 'subtab2') {
4608
                    $('#frmModel').show();
4609
                } else {
4610
                    $('#frmModel').hide();
4611
                }
4612
            });*/
4613
4614
          function deleteItem(event) {
4615
            var id = $(event).attr('data-id');
4616
            var title = $(event).attr('data-title');
4617
            var params = '&a=delete_item&id=' + id;
4618
            if (confirmation(title)) {
4619
                $.get(
4620
                    '".$ajax_url."',
4621
                    params,
4622
                    function(result) {
4623
                        refreshTree();
4624
                    }
4625
                );
4626
            }
4627
        }
4628
        </script>";
4629
4630
        $content .= $this->return_new_tree($updateAudio, $dropElementHere);
4631
        $documentId = isset($_GET['path_item']) ? (int) $_GET['path_item'] : 0;
4632
4633
        $repo = Container::getDocumentRepository();
4634
        $document = $repo->find($documentId);
4635
        if ($document) {
4636
            // Show the template list
4637
            $content .= '<div id="frmModel" class="scrollbar-inner lp-add-item"></div>';
4638
        }
4639
4640
        // Show the template list.
4641
        if (('document' === $type || 'step' === $type) && !isset($_GET['file'])) {
4642
            // Show the template list.
4643
            $content .= '<div id="frmModel" class="scrollbar-inner lp-add-item"></div>';
4644
        }
4645
4646
        return $content;
4647
    }
4648
4649
    /**
4650
     * @param bool  $updateAudio
4651
     * @param bool   $dropElement
4652
     *
4653
     * @return string
4654
     */
4655
    public function return_new_tree($updateAudio = false, $dropElement = false)
4656
    {
4657
        $list = $this->getBuildTree(false, $dropElement);
4658
        $return = Display::panelCollapse(
4659
            $this->name,
4660
            $list,
4661
            'scorm-list',
4662
            null,
4663
            'scorm-list-accordion',
4664
            'scorm-list-collapse'
4665
        );
4666
4667
        if ($updateAudio) {
4668
            //$return = $result['return_audio'];
4669
        }
4670
4671
        return $return;
4672
    }
4673
4674
    public function getBuildTree($noWrapper = false, $dropElement = false)
4675
    {
4676
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
4677
4678
        $upIcon = Display::return_icon(
4679
            'up.png',
4680
            get_lang('Up'),
4681
            [],
4682
            ICON_SIZE_TINY
4683
        );
4684
4685
        $disableUpIcon = Display::return_icon(
4686
            'up_na.png',
4687
            get_lang('Up'),
4688
            [],
4689
            ICON_SIZE_TINY
4690
        );
4691
4692
        $downIcon = Display::return_icon(
4693
            'down.png',
4694
            get_lang('Down'),
4695
            [],
4696
            ICON_SIZE_TINY
4697
        );
4698
4699
        $previewImage = Display::return_icon(
4700
            'preview_view.png',
4701
            get_lang('Preview'),
4702
            [],
4703
            ICON_SIZE_TINY
4704
        );
4705
        $lpItemRepo = Container::getLpItemRepository();
4706
        $itemRoot = $lpItemRepo->getRootItem($this->get_id());
4707
4708
        $options = [
4709
            'decorate' => true,
4710
            'rootOpen' => function($tree) use ($noWrapper) {
4711
                if ($tree[0]['lvl'] === 1) {
4712
                    if ($noWrapper) {
4713
                        return '';
4714
                    }
4715
                    return '<ul id="lp_item_list" class="list-group nested-sortable">';
4716
                }
4717
4718
                return '<ul class="list-group nested-sortable">';
4719
            },
4720
            'rootClose' => function($tree) use ($noWrapper, $dropElement)  {
4721
                if ($tree[0]['lvl'] === 1) {
4722
                    if ($dropElement) {
4723
                        //return Display::return_message(get_lang('Drag and drop an element here'));
4724
                        //return $this->getDropElementHtml();
4725
                    }
4726
                    if ($noWrapper) {
4727
                        return '';
4728
                    }
4729
                }
4730
4731
                return '</ul>';
4732
            },
4733
            'childOpen' => function($child) {
4734
                $id = $child['iid'];
4735
                return '<li
4736
                    id="'.$id.'"
4737
                    data-id="'.$id.'"
4738
                    class=" flex flex-col list-group-item nested-'.$child['lvl'].'">';
4739
            },
4740
            'childClose' => '',
4741
            'nodeDecorator' => function ($node) use ($mainUrl, $previewImage, $upIcon, $downIcon) {
4742
                $fullTitle = $node['title'];
4743
                $title = cut($fullTitle, self::MAX_LP_ITEM_TITLE_LENGTH);
4744
                $itemId = $node['iid'];
4745
                $type = $node['itemType'];
4746
                $lpId = $this->get_id();
4747
4748
                $moveIcon = '';
4749
                if (TOOL_LP_FINAL_ITEM !== $type) {
4750
                    $moveIcon .= '<a class="moved" href="#">';
4751
                    $moveIcon .= Display::return_icon(
4752
                        'move_everywhere.png',
4753
                        get_lang('Move'),
4754
                        [],
4755
                        ICON_SIZE_TINY
4756
                    );
4757
                    $moveIcon .= '</a>';
4758
                }
4759
4760
                $iconName = str_replace(' ', '', $type);
4761
                $icon = Display::return_icon(
4762
                    'lp_'.$iconName.'.png',
4763
                    '',
4764
                    [],
4765
                    ICON_SIZE_TINY
4766
                );
4767
4768
                $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$itemId.'&lp_id='.$lpId;
4769
                $previewIcon = Display::url(
4770
                    $previewImage,
4771
                    $urlPreviewLink,
4772
                    [
4773
                        'target' => '_blank',
4774
                        'class' => 'btn btn-default',
4775
                        'data-title' => $title,
4776
                        'title' => $title,
4777
                    ]
4778
                );
4779
                $url = $mainUrl.'&view=build&id='.$itemId.'&lp_id='.$lpId;
4780
4781
                $preRequisitiesIcon = Display::url(
4782
                    Display::return_icon(
4783
                        'accept.png',
4784
                        get_lang('Prerequisites'),
4785
                        [],
4786
                        ICON_SIZE_TINY
4787
                    ),
4788
                    $url.'&action=edit_item_prereq',
4789
                    ['class' => 'btn btn-default']
4790
                );
4791
4792
                //$editIcon = '';
4793
                $editIcon = '<a
4794
                    href="'.$mainUrl.'&action=edit_item&view=build&id='.$itemId.'&lp_id='.$lpId.'&path_item='.$node['path'].'"
4795
                    class="btn btn-default"
4796
                    >';
4797
                $editIcon .= Display::return_icon(
4798
                    'edit.png',
4799
                    get_lang('Edit section description/name'),
4800
                    [],
4801
                    ICON_SIZE_TINY
4802
                );
4803
                $editIcon .= '</a>';
4804
                $orderIcons = '';
4805
                /*if ('final_item' !== $type) {
4806
                    $orderIcons = Display::url(
4807
                        $upIcon,
4808
                        'javascript:void(0)',
4809
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $itemId]
4810
                    );
4811
                    $orderIcons .= Display::url(
4812
                        $downIcon,
4813
                        'javascript:void(0)',
4814
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $itemId]
4815
                    );
4816
                }*/
4817
4818
                $deleteIcon = ' <a
4819
                    data-id = '.$itemId.'
4820
                    data-title = \''.addslashes($title).'\'
4821
                    href="javascript:void(0);"
4822
                    onclick="return deleteItem(this);"
4823
                    class="btn btn-default">';
4824
                $deleteIcon .= Display::return_icon(
4825
                    'delete.png',
4826
                    get_lang('Delete section'),
4827
                    [],
4828
                    ICON_SIZE_TINY
4829
                );
4830
                $deleteIcon .= '</a>';
4831
                $extra = '';
4832
4833
                if ('dir' === $type && empty($node['__children'])) {
4834
                    $level = $node['lvl'] + 1;
4835
                    $extra = '<ul class="list-group nested-sortable">
4836
                                <li class="list-group-item list-group-item-empty nested-'.$level.'"></li>
4837
                              </ul>';
4838
                }
4839
                //return $title;
4840
                $buttons = Display::tag(
4841
                    'div',
4842
                    "<div class=\"btn-group btn-group-sm\">
4843
                                $editIcon
4844
                                $preRequisitiesIcon
4845
                                $orderIcons
4846
                                $deleteIcon
4847
                               </div>",
4848
                    ['class' => 'btn-toolbar button_actions']
4849
                );
4850
                //$buttons = '';
4851
                //$extra = '';
4852
                //Display::span($title, ['title' => $fullTitle])
4853
                //return $title.                $extra;
4854
                return
4855
                    "<div class='flex flex-row'> $moveIcon  $icon <div>$title </div></div>
4856
                    $extra
4857
                    $buttons
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
        $prerequisiteId = $this->entity->getPrerequisite();
6443
6444
        $repo = Container::getLpRepository();
6445
        $qb = $repo->findAllByCourse(api_get_course_entity(), api_get_session_entity());
6446
        /** @var CLp[] $lps */
6447
        $lps = $qb->getQuery()->getResult();
6448
6449
        //$session_id = api_get_session_id();
6450
        /*$session_condition = api_get_session_condition($session_id, true, true);
6451
        $sql = "SELECT * FROM $tbl_lp
6452
                WHERE c_id = $course_id $session_condition
6453
                ORDER BY display_order ";
6454
        $rs = Database::query($sql);*/
6455
6456
        $items = [get_lang('none')];
6457
        foreach ($lps as $lp) {
6458
            $myLpId = $lp->getIid();
6459
            if ($myLpId == $lp_id) {
6460
                continue;
6461
            }
6462
            $items[$myLpId] = $lp->getName();
6463
            /*$return .= '<option
6464
                value="'.$myLpId.'" '.(($myLpId == $prerequisiteId) ? ' selected ' : '').'>'.
6465
                $lp->getName().
6466
                '</option>';*/
6467
        }
6468
6469
        $select = $form->addSelect('prerequisites', get_lang('Prerequisites'), $items);
6470
        $select->setSelected($prerequisiteId);
6471
    }
6472
6473
    /**
6474
     * Creates a list with all the documents in it.
6475
     *
6476
     * @param bool $showInvisibleFiles
6477
     *
6478
     * @throws Exception
6479
     *
6480
     *
6481
     * @return string
6482
     */
6483
    public function get_documents($showInvisibleFiles = false)
6484
    {
6485
        $sessionId = api_get_session_id();
6486
        $documentTree = DocumentManager::get_document_preview(
6487
            api_get_course_entity(),
6488
            $this->lp_id,
6489
            null,
6490
            $sessionId,
6491
            true,
6492
            null,
6493
            null,
6494
            $showInvisibleFiles,
6495
            true
6496
        );
6497
6498
        $form = new FormValidator(
6499
            'form_upload',
6500
            'POST',
6501
            $this->getCurrentBuildingModeURL(),
6502
            '',
6503
            ['enctype' => 'multipart/form-data']
6504
        );
6505
6506
        $folders = DocumentManager::get_all_document_folders(
6507
            api_get_course_info(),
6508
            0,
6509
            true
6510
        );
6511
6512
        $folder = $this->generate_lp_folder(api_get_course_info());
6513
6514
        DocumentManager::build_directory_selector(
6515
            $folders,
6516
            $folder->getIid(),
6517
            [],
6518
            true,
6519
            $form,
6520
            'directory_parent_id'
6521
        );
6522
6523
        $group = [
6524
            $form->createElement(
6525
                'radio',
6526
                'if_exists',
6527
                get_lang('If file exists:'),
6528
                get_lang('Do nothing'),
6529
                'nothing'
6530
            ),
6531
            $form->createElement(
6532
                'radio',
6533
                'if_exists',
6534
                null,
6535
                get_lang('Overwrite the existing file'),
6536
                'overwrite'
6537
            ),
6538
            $form->createElement(
6539
                'radio',
6540
                'if_exists',
6541
                null,
6542
                get_lang('Rename the uploaded file if it exists'),
6543
                'rename'
6544
            ),
6545
        ];
6546
        $form->addGroup($group, null, get_lang('If file exists:'));
6547
6548
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
6549
        $defaultFileExistsOption = 'rename';
6550
        if (!empty($fileExistsOption)) {
6551
            $defaultFileExistsOption = $fileExistsOption;
6552
        }
6553
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
6554
6555
        // Check box options
6556
        $form->addCheckBox(
6557
            'unzip',
6558
            get_lang('Options'),
6559
            get_lang('Uncompress zip')
6560
        );
6561
6562
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
6563
        $form->addMultipleUpload($url);
6564
6565
        $lpItem = (new CLpItem())
6566
            ->setTitle('')
6567
            ->setItemType(TOOL_DOCUMENT)
6568
        ;
6569
        $new = $this->displayDocumentForm('add', $lpItem);
6570
6571
        /*$lpItem = new CLpItem();
6572
        $lpItem->setItemType(TOOL_READOUT_TEXT);
6573
        $frmReadOutText = $this->displayDocumentForm('add');*/
6574
6575
        $headers = [
6576
            get_lang('Files'),
6577
            get_lang('Create a new document'),
6578
            //get_lang('Create read-out text'),
6579
            get_lang('Upload'),
6580
        ];
6581
6582
        return Display::tabs(
6583
            $headers,
6584
            [$documentTree, $new, $form->returnForm()],
6585
            'subtab'
6586
        );
6587
    }
6588
6589
    /**
6590
     * Creates a list with all the exercises (quiz) in it.
6591
     *
6592
     * @return string
6593
     */
6594
    public function get_exercises()
6595
    {
6596
        $course_id = api_get_course_int_id();
6597
        $session_id = api_get_session_id();
6598
        $setting = 'true' === api_get_setting('lp.show_invisible_exercise_in_lp_toc');
6599
6600
        //$activeCondition = ' active <> -1 ';
6601
        $active = 2;
6602
        if ($setting) {
6603
            $active = 1;
6604
            //$activeCondition = ' active = 1 ';
6605
        }
6606
6607
        $categoryCondition = '';
6608
6609
        $keyword = $_REQUEST['keyword'] ?? null;
6610
        $categoryId = $_REQUEST['category_id'] ?? null;
6611
        /*if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
6612
            $categoryCondition = " AND exercise_category_id = $categoryId ";
6613
        }
6614
6615
        $keywordCondition = '';
6616
6617
        if (!empty($keyword)) {
6618
            $keyword = Database::escape_string($keyword);
6619
            $keywordCondition = " AND title LIKE '%$keyword%' ";
6620
        }
6621
        */
6622
        $course = api_get_course_entity($course_id);
6623
        $session = api_get_session_entity($session_id);
6624
6625
        $qb = Container::getQuizRepository()->findAllByCourse($course, $session, $keyword, $active, false, $categoryId);
6626
        /** @var CQuiz[] $exercises */
6627
        $exercises = $qb->getQuery()->getResult();
6628
6629
        /*$sql_quiz = "SELECT * FROM $tbl_quiz
6630
                     WHERE
6631
                            c_id = $course_id AND
6632
                            $activeCondition
6633
                            $condition_session
6634
                            $categoryCondition
6635
                            $keywordCondition
6636
                     ORDER BY title ASC";
6637
        $res_quiz = Database::query($sql_quiz);*/
6638
6639
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
6640
6641
        // Create a search-box
6642
        /*$form = new FormValidator('search_simple', 'get', $currentUrl);
6643
        $form->addHidden('action', 'add_item');
6644
        $form->addHidden('type', 'step');
6645
        $form->addHidden('lp_id', $this->lp_id);
6646
        $form->addHidden('lp_build_selected', '2');
6647
6648
        $form->addCourseHiddenParams();
6649
        $form->addText(
6650
            'keyword',
6651
            get_lang('Search'),
6652
            false,
6653
            [
6654
                'aria-label' => get_lang('Search'),
6655
            ]
6656
        );
6657
6658
        if (api_get_configuration_value('allow_exercise_categories')) {
6659
            $manager = new ExerciseCategoryManager();
6660
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
6661
            if (!empty($options)) {
6662
                $form->addSelect(
6663
                    'category_id',
6664
                    get_lang('Category'),
6665
                    $options,
6666
                    ['placeholder' => get_lang('Please select an option')]
6667
                );
6668
            }
6669
        }
6670
6671
        $form->addButtonSearch(get_lang('Search'));
6672
        $return = $form->returnForm();*/
6673
6674
        $return = '<ul class = "list-group lp_resource">';
6675
        $return .= '<li class="list-group-item lp_resource_element disable_drag">';
6676
        $return .= Display::return_icon('new_exercice.png');
6677
        $return .= '<a
6678
            href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
6679
            get_lang('New test').'</a>';
6680
        $return .= '</li>';
6681
6682
        $previewIcon = Display::return_icon(
6683
            'preview_view.png',
6684
            get_lang('Preview')
6685
        );
6686
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
6687
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
6688
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
6689
        foreach ($exercises as $exercise) {
6690
            $exerciseId = $exercise->getIid();
6691
            $title = strip_tags(api_html_entity_decode($exercise->getTitle()));
6692
            $visibility = $exercise->isVisible($course, $session);
6693
6694
            $link = Display::url(
6695
                $previewIcon,
6696
                $exerciseUrl.'&exerciseId='.$exerciseId,
6697
                ['target' => '_blank']
6698
            );
6699
            $return .= '<li
6700
                class="list-group-item lp_resource_element"
6701
                id="'.$exerciseId.'"
6702
                data-id="'.$exerciseId.'"
6703
                title="'.$title.'">';
6704
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
6705
            $return .= $quizIcon;
6706
            $sessionStar = '';
6707
            /*$sessionStar = api_get_session_image(
6708
                $row_quiz['session_id'],
6709
                $userInfo['status']
6710
            );*/
6711
            $return .= Display::url(
6712
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
6713
                api_get_self().'?'.
6714
                    api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$exerciseId.'&lp_id='.$this->lp_id,
6715
                [
6716
                    'class' => false === $visibility ? 'moved text-muted ' : 'moved link_with_id',
6717
                    'data_type' => 'quiz',
6718
                    'data-id' => $exerciseId,
6719
                ]
6720
            );
6721
            $return .= '</li>';
6722
        }
6723
6724
        $return .= '</ul>';
6725
6726
        return $return;
6727
    }
6728
6729
    /**
6730
     * Creates a list with all the links in it.
6731
     *
6732
     * @return string
6733
     */
6734
    public function get_links()
6735
    {
6736
        $sessionId = api_get_session_id();
6737
        $repo = Container::getLinkRepository();
6738
6739
        $course = api_get_course_entity();
6740
        $session = api_get_session_entity($sessionId);
6741
        $qb = $repo->getResourcesByCourse($course, $session);
6742
        /** @var CLink[] $links */
6743
        $links = $qb->getQuery()->getResult();
6744
6745
        $selfUrl = api_get_self();
6746
        $courseIdReq = api_get_cidreq();
6747
        $userInfo = api_get_user_info();
6748
6749
        $moveEverywhereIcon = Display::return_icon(
6750
            'move_everywhere.png',
6751
            get_lang('Move'),
6752
            [],
6753
            ICON_SIZE_TINY
6754
        );
6755
6756
        $categorizedLinks = [];
6757
        $categories = [];
6758
6759
        foreach ($links as $link) {
6760
            $categoryId = null !== $link->getCategory() ? $link->getCategory()->getIid() : 0;
6761
            if (empty($categoryId)) {
6762
                $categories[0] = get_lang('Uncategorized');
6763
            } else {
6764
                $category = $link->getCategory();
6765
                $categories[$categoryId] = $category->getCategoryTitle();
6766
            }
6767
            $categorizedLinks[$categoryId][$link->getIid()] = $link;
6768
        }
6769
6770
        $linksHtmlCode =
6771
            '<script>
6772
            function toggle_tool(tool, id) {
6773
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
6774
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
6775
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
6776
                } else {
6777
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
6778
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
6779
                }
6780
            }
6781
        </script>
6782
6783
        <ul class="list-group lp_resource">
6784
            <li class="list-group-item lp_resource_element disable_drag ">
6785
                '.Display::return_icon('linksnew.gif').'
6786
                <a
6787
                href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'"
6788
                title="'.get_lang('Add a link').'">'.
6789
                get_lang('Add a link').'
6790
                </a>
6791
            </li>';
6792
        $linkIcon = Display::return_icon('links.png', '', [], ICON_SIZE_TINY);
6793
        foreach ($categorizedLinks as $categoryId => $links) {
6794
            $linkNodes = null;
6795
            /** @var CLink $link */
6796
            foreach ($links as $key => $link) {
6797
                $title = $link->getTitle();
6798
                $id = $link->getIid();
6799
                $linkUrl = Display::url(
6800
                    Display::return_icon('preview_view.png', get_lang('Preview')),
6801
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
6802
                    ['target' => '_blank']
6803
                );
6804
6805
                if ($link->isVisible($course, $session)) {
6806
                    //$sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
6807
                    $sessionStar = '';
6808
                    $url = $selfUrl.'?'.$courseIdReq.'&action=add_item&type='.TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id;
6809
                    $link = Display::url(
6810
                        Security::remove_XSS($title).$sessionStar.$linkUrl,
6811
                        $url,
6812
                        [
6813
                            'class' => 'moved link_with_id',
6814
                            'data-id' => $key,
6815
                            'data_type' => TOOL_LINK,
6816
                            'title' => $title,
6817
                        ]
6818
                    );
6819
                    $linkNodes .=
6820
                        "<li
6821
                            class='list-group-item lp_resource_element'
6822
                            id= $id
6823
                            data-id= $id
6824
                            >
6825
                         <a class='moved' href='#'>
6826
                            $moveEverywhereIcon
6827
                        </a>
6828
                        $linkIcon $link
6829
                        </li>";
6830
                }
6831
            }
6832
            $linksHtmlCode .=
6833
                '<li class="list-group-item disable_drag">
6834
                    <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" >
6835
                        <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
6836
                        align="absbottom" />
6837
                    </a>
6838
                    <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
6839
                </li>
6840
            '.
6841
                $linkNodes.
6842
            '';
6843
            //<div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.
6844
        }
6845
        $linksHtmlCode .= '</ul>';
6846
6847
        return $linksHtmlCode;
6848
    }
6849
6850
    /**
6851
     * Creates a list with all the student publications in it.
6852
     *
6853
     * @return string
6854
     */
6855
    public function get_student_publications()
6856
    {
6857
        $return = '<ul class="list-group lp_resource">';
6858
        $return .= '<li class="list-group-item lp_resource_element">';
6859
        $works = getWorkListTeacher(0, 100, null, null, null);
6860
        if (!empty($works)) {
6861
            $icon = Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
6862
            foreach ($works as $work) {
6863
                $workId = $work['iid'];
6864
                $link = Display::url(
6865
                    Display::return_icon('preview_view.png', get_lang('Preview')),
6866
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$workId,
6867
                    ['target' => '_blank']
6868
                );
6869
6870
                $return .= '<li
6871
                    class="list-group-item lp_resource_element"
6872
                    id="'.$workId.'"
6873
                    data-id="'.$workId.'"
6874
                    >';
6875
                $return .= '<a class="moved" href="#">';
6876
                $return .= Display::return_icon(
6877
                    'move_everywhere.png',
6878
                    get_lang('Move'),
6879
                    [],
6880
                    ICON_SIZE_TINY
6881
                );
6882
                $return .= '</a> ';
6883
6884
                $return .= $icon;
6885
                $return .= Display::url(
6886
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link,
6887
                    api_get_self().'?'.
6888
                    api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&file='.$work['iid'].'&lp_id='.$this->lp_id,
6889
                    [
6890
                        'class' => 'moved link_with_id',
6891
                        'data-id' => $work['iid'],
6892
                        'data_type' => TOOL_STUDENTPUBLICATION,
6893
                        'title' => Security::remove_XSS(cut(strip_tags($work['title']), 80)),
6894
                    ]
6895
                );
6896
                $return .= '</li>';
6897
            }
6898
        }
6899
6900
        $return .= '</ul>';
6901
6902
        return $return;
6903
    }
6904
6905
    /**
6906
     * Creates a list with all the forums in it.
6907
     *
6908
     * @return string
6909
     */
6910
    public function get_forums()
6911
    {
6912
        $forumCategories = get_forum_categories();
6913
        $forumsInNoCategory = get_forums_in_category(0);
6914
        if (!empty($forumsInNoCategory)) {
6915
            $forumCategories = array_merge(
6916
                $forumCategories,
6917
                [
6918
                    [
6919
                        'cat_id' => 0,
6920
                        'session_id' => 0,
6921
                        'visibility' => 1,
6922
                        'cat_comment' => null,
6923
                    ],
6924
                ]
6925
            );
6926
        }
6927
6928
        $a_forums = [];
6929
        $courseEntity = api_get_course_entity(api_get_course_int_id());
6930
        $sessionEntity = api_get_session_entity(api_get_session_id());
6931
6932
        foreach ($forumCategories as $forumCategory) {
6933
            // The forums in this category.
6934
            $forumsInCategory = get_forums_in_category($forumCategory->getIid());
6935
            if (!empty($forumsInCategory)) {
6936
                foreach ($forumsInCategory as $forum) {
6937
                    if ($forum->isVisible($courseEntity, $sessionEntity)) {
6938
                        $a_forums[] = $forum;
6939
                    }
6940
                }
6941
            }
6942
        }
6943
6944
        $return = '<ul class="list-group lp_resource">';
6945
6946
        // First add link
6947
        $return .= '<li class="list-group-item lp_resource_element disable_drag">';
6948
        $return .= Display::return_icon('new_forum.png');
6949
        $return .= Display::url(
6950
            get_lang('Create a new forum'),
6951
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
6952
                'action' => 'add',
6953
                'content' => 'forum',
6954
                'lp_id' => $this->lp_id,
6955
            ]),
6956
            ['title' => get_lang('Create a new forum')]
6957
        );
6958
        $return .= '</li>';
6959
6960
        $return .= '<script>
6961
            function toggle_forum(forum_id) {
6962
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
6963
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
6964
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
6965
                } else {
6966
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
6967
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
6968
                }
6969
            }
6970
        </script>';
6971
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
6972
        foreach ($a_forums as $forum) {
6973
            $forumId = $forum->getIid();
6974
            $title = Security::remove_XSS($forum->getForumTitle());
6975
            $link = Display::url(
6976
                Display::return_icon('preview_view.png', get_lang('Preview')),
6977
                api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forumId,
6978
                ['target' => '_blank']
6979
            );
6980
6981
            $return .= '<li
6982
                    class="list-group-item lp_resource_element"
6983
                    id="'.$forumId.'"
6984
                    data-id="'.$forumId.'"
6985
                    >';
6986
            $return .= '<a class="moved" href="#">';
6987
            $return .= $moveIcon;
6988
            $return .= ' </a>';
6989
            $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
6990
6991
            $moveLink = Display::url(
6992
                $title.' '.$link,
6993
                api_get_self().'?'.
6994
                api_get_cidreq().'&action=add_item&type='.TOOL_FORUM.'&forum_id='.$forumId.'&lp_id='.$this->lp_id,
6995
                [
6996
                    'class' => 'moved link_with_id',
6997
                    'data-id' => $forumId,
6998
                    'data_type' => TOOL_FORUM,
6999
                    'title' => $title,
7000
                    'style' => 'vertical-align:middle',
7001
                ]
7002
            );
7003
            $return .= '<a onclick="javascript:toggle_forum('.$forumId.');" style="cursor:hand; vertical-align:middle">
7004
                            <img
7005
                                src="'.Display::returnIconPath('add.png').'"
7006
                                id="forum_'.$forumId.'_opener" align="absbottom"
7007
                             />
7008
                        </a>
7009
                        '.$moveLink;
7010
            $return .= '</li>';
7011
7012
            $return .= '<div style="display:none" id="forum_'.$forumId.'_content">';
7013
            $threads = get_threads($forumId);
7014
            if (is_array($threads)) {
7015
                foreach ($threads as $thread) {
7016
                    $threadId = $thread->getIid();
7017
                    $link = Display::url(
7018
                        Display::return_icon('preview_view.png', get_lang('Preview')),
7019
                        api_get_path(WEB_CODE_PATH).
7020
                        'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forumId.'&thread='.$threadId,
7021
                        ['target' => '_blank']
7022
                    );
7023
7024
                    $return .= '<li
7025
                        class="list-group-item lp_resource_element"
7026
                      id="'.$threadId.'"
7027
                        data-id="'.$threadId.'"
7028
                    >';
7029
                    $return .= '&nbsp;<a class="moved" href="#">';
7030
                    $return .= $moveIcon;
7031
                    $return .= ' </a>';
7032
                    $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
7033
                    $return .= '<a
7034
                        class="moved link_with_id"
7035
                        data-id="'.$threadId.'"
7036
                        data_type="'.TOOL_THREAD.'"
7037
                        title="'.$thread->getThreadTitle().'"
7038
                        href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_THREAD.'&thread_id='.$threadId.'&lp_id='.$this->lp_id.'"
7039
                        >'.
7040
                        Security::remove_XSS($thread->getThreadTitle()).' '.$link.'</a>';
7041
                    $return .= '</li>';
7042
                }
7043
            }
7044
            $return .= '</div>';
7045
        }
7046
        $return .= '</ul>';
7047
7048
        return $return;
7049
    }
7050
7051
    /**
7052
     * Temp function to be moved in main_api or the best place around for this.
7053
     * Creates a file path if it doesn't exist.
7054
     *
7055
     * @param string $path
7056
     */
7057
    public function create_path($path)
7058
    {
7059
        $path_bits = explode('/', dirname($path));
7060
7061
        // IS_WINDOWS_OS has been defined in main_api.lib.php
7062
        $path_built = IS_WINDOWS_OS ? '' : '/';
7063
        foreach ($path_bits as $bit) {
7064
            if (!empty($bit)) {
7065
                $new_path = $path_built.$bit;
7066
                if (is_dir($new_path)) {
7067
                    $path_built = $new_path.'/';
7068
                } else {
7069
                    mkdir($new_path, api_get_permissions_for_new_directories());
7070
                    $path_built = $new_path.'/';
7071
                }
7072
            }
7073
        }
7074
    }
7075
7076
    /**
7077
     * @param int    $lp_id
7078
     * @param string $status
7079
     */
7080
    public function set_autolaunch($lp_id, $status)
7081
    {
7082
        $course_id = api_get_course_int_id();
7083
        $lp_id = (int) $lp_id;
7084
        $status = (int) $status;
7085
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
7086
7087
        // Setting everything to autolaunch = 0
7088
        $attributes['autolaunch'] = 0;
7089
        $where = [
7090
            'session_id = ? AND c_id = ? ' => [
7091
                api_get_session_id(),
7092
                $course_id,
7093
            ],
7094
        ];
7095
        Database::update($lp_table, $attributes, $where);
7096
        if (1 == $status) {
7097
            //Setting my lp_id to autolaunch = 1
7098
            $attributes['autolaunch'] = 1;
7099
            $where = [
7100
                'iid = ? AND session_id = ? AND c_id = ?' => [
7101
                    $lp_id,
7102
                    api_get_session_id(),
7103
                    $course_id,
7104
                ],
7105
            ];
7106
            Database::update($lp_table, $attributes, $where);
7107
        }
7108
    }
7109
7110
    /**
7111
     * Gets previous_item_id for the next element of the lp_item table.
7112
     *
7113
     * @author Isaac flores paz
7114
     *
7115
     * @return int Previous item ID
7116
     */
7117
    public function select_previous_item_id()
7118
    {
7119
        $course_id = api_get_course_int_id();
7120
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7121
7122
        // Get the max order of the items
7123
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
7124
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7125
        $rs_max_order = Database::query($sql);
7126
        $row_max_order = Database::fetch_object($rs_max_order);
7127
        $max_order = $row_max_order->display_order;
7128
        // Get the previous item ID
7129
        $sql = "SELECT iid as previous FROM $table_lp_item
7130
                WHERE
7131
                    c_id = $course_id AND
7132
                    lp_id = ".$this->lp_id." AND
7133
                    display_order = '$max_order' ";
7134
        $rs_max = Database::query($sql);
7135
        $row_max = Database::fetch_object($rs_max);
7136
7137
        // Return the previous item ID
7138
        return $row_max->previous;
7139
    }
7140
7141
    /**
7142
     * Copies an LP.
7143
     */
7144
    public function copy()
7145
    {
7146
        // Course builder
7147
        $cb = new CourseBuilder();
7148
7149
        //Setting tools that will be copied
7150
        $cb->set_tools_to_build(['learnpaths']);
7151
7152
        //Setting elements that will be copied
7153
        $cb->set_tools_specific_id_list(
7154
            ['learnpaths' => [$this->lp_id]]
7155
        );
7156
7157
        $course = $cb->build();
7158
7159
        //Course restorer
7160
        $course_restorer = new CourseRestorer($course);
7161
        $course_restorer->set_add_text_in_items(true);
7162
        $course_restorer->set_tool_copy_settings(
7163
            ['learnpaths' => ['reset_dates' => true]]
7164
        );
7165
        $course_restorer->restore(
7166
            api_get_course_id(),
7167
            api_get_session_id(),
7168
            false,
7169
            false
7170
        );
7171
    }
7172
7173
    /**
7174
     * Verify document size.
7175
     *
7176
     * @param string $s
7177
     *
7178
     * @return bool
7179
     */
7180
    public static function verify_document_size($s)
7181
    {
7182
        $post_max = ini_get('post_max_size');
7183
        if ('M' == substr($post_max, -1, 1)) {
7184
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
7185
        } elseif ('G' == substr($post_max, -1, 1)) {
7186
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
7187
        }
7188
        $upl_max = ini_get('upload_max_filesize');
7189
        if ('M' == substr($upl_max, -1, 1)) {
7190
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
7191
        } elseif ('G' == substr($upl_max, -1, 1)) {
7192
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
7193
        }
7194
7195
        $repo = Container::getDocumentRepository();
7196
        $documents_total_space = $repo->getTotalSpace(api_get_course_int_id());
7197
7198
        $course_max_space = DocumentManager::get_course_quota();
7199
        $total_size = filesize($s) + $documents_total_space;
7200
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
7201
            return true;
7202
        }
7203
7204
        return false;
7205
    }
7206
7207
    /**
7208
     * Clear LP prerequisites.
7209
     */
7210
    public function clearPrerequisites()
7211
    {
7212
        $course_id = $this->get_course_int_id();
7213
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7214
        $lp_id = $this->get_id();
7215
        // Cleaning prerequisites
7216
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
7217
                WHERE lp_id = $lp_id";
7218
        Database::query($sql);
7219
7220
        // Cleaning mastery score for exercises
7221
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
7222
                WHERE lp_id = $lp_id AND item_type = 'quiz'";
7223
        Database::query($sql);
7224
    }
7225
7226
    public function set_previous_step_as_prerequisite_for_all_items()
7227
    {
7228
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7229
        $course_id = $this->get_course_int_id();
7230
        $lp_id = $this->get_id();
7231
7232
        if (!empty($this->items)) {
7233
            $previous_item_id = null;
7234
            $previous_item_max = 0;
7235
            $previous_item_type = null;
7236
            $last_item_not_dir = null;
7237
            $last_item_not_dir_type = null;
7238
            $last_item_not_dir_max = null;
7239
7240
            foreach ($this->ordered_items as $itemId) {
7241
                $item = $this->getItem($itemId);
7242
                // if there was a previous item... (otherwise jump to set it)
7243
                if (!empty($previous_item_id)) {
7244
                    $current_item_id = $item->get_id(); //save current id
7245
                    if ('dir' != $item->get_type()) {
7246
                        // Current item is not a folder, so it qualifies to get a prerequisites
7247
                        if ('quiz' == $last_item_not_dir_type) {
7248
                            // if previous is quiz, mark its max score as default score to be achieved
7249
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
7250
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
7251
                            Database::query($sql);
7252
                        }
7253
                        // now simply update the prerequisite to set it to the last non-chapter item
7254
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
7255
                                WHERE lp_id = $lp_id AND iid = $current_item_id";
7256
                        Database::query($sql);
7257
                        // record item as 'non-chapter' reference
7258
                        $last_item_not_dir = $item->get_id();
7259
                        $last_item_not_dir_type = $item->get_type();
7260
                        $last_item_not_dir_max = $item->get_max();
7261
                    }
7262
                } else {
7263
                    if ('dir' != $item->get_type()) {
7264
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
7265
                        $last_item_not_dir = $item->get_id();
7266
                        $last_item_not_dir_type = $item->get_type();
7267
                        $last_item_not_dir_max = $item->get_max();
7268
                    }
7269
                }
7270
                // Saving the item as "previous item" for the next loop
7271
                $previous_item_id = $item->get_id();
7272
                $previous_item_max = $item->get_max();
7273
                $previous_item_type = $item->get_type();
7274
            }
7275
        }
7276
    }
7277
7278
    /**
7279
     * @param array $params
7280
     *
7281
     * @return int
7282
     */
7283
    public static function createCategory($params)
7284
    {
7285
        $courseEntity = api_get_course_entity(api_get_course_int_id());
7286
7287
        $item = new CLpCategory();
7288
        $item
7289
            ->setName($params['name'])
7290
            ->setParent($courseEntity)
7291
            ->addCourseLink($courseEntity, api_get_session_entity())
7292
        ;
7293
7294
        $repo = Container::getLpCategoryRepository();
7295
        $repo->create($item);
7296
7297
        return $item->getIid();
7298
    }
7299
7300
    /**
7301
     * @param array $params
7302
     */
7303
    public static function updateCategory($params)
7304
    {
7305
        $em = Database::getManager();
7306
        /** @var CLpCategory $item */
7307
        $item = $em->find(CLpCategory::class, $params['id']);
7308
        if ($item) {
7309
            $item->setName($params['name']);
7310
            $em->persist($item);
7311
            $em->flush();
7312
        }
7313
    }
7314
7315
    /**
7316
     * @param int $id
7317
     */
7318
    public static function moveUpCategory($id)
7319
    {
7320
        $id = (int) $id;
7321
        $em = Database::getManager();
7322
        /** @var CLpCategory $item */
7323
        $item = $em->find(CLpCategory::class, $id);
7324
        if ($item) {
7325
            $position = $item->getPosition() - 1;
7326
            $item->setPosition($position);
7327
            $em->persist($item);
7328
            $em->flush();
7329
        }
7330
    }
7331
7332
    /**
7333
     * @param int $id
7334
     */
7335
    public static function moveDownCategory($id)
7336
    {
7337
        $id = (int) $id;
7338
        $em = Database::getManager();
7339
        /** @var CLpCategory $item */
7340
        $item = $em->find(CLpCategory::class, $id);
7341
        if ($item) {
7342
            $position = $item->getPosition() + 1;
7343
            $item->setPosition($position);
7344
            $em->persist($item);
7345
            $em->flush();
7346
        }
7347
    }
7348
7349
    /**
7350
     * @param int $courseId
7351
     *
7352
     * @return int
7353
     */
7354
    public static function getCountCategories($courseId)
7355
    {
7356
        if (empty($courseId)) {
7357
            return 0;
7358
        }
7359
        $repo = Container::getLpCategoryRepository();
7360
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId));
7361
        $qb->addSelect('count(resource)');
7362
7363
        return (int) $qb->getQuery()->getSingleScalarResult();
7364
    }
7365
7366
    /**
7367
     * @param int $courseId
7368
     *
7369
     * @return CLpCategory[]
7370
     */
7371
    public static function getCategories($courseId)
7372
    {
7373
        // Using doctrine extensions
7374
        $repo = Container::getLpCategoryRepository();
7375
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId));
7376
7377
        return $qb->getQuery()->getResult();
7378
    }
7379
7380
    public static function getCategorySessionId($id)
7381
    {
7382
        if (false === api_get_configuration_value('allow_session_lp_category')) {
7383
            return 0;
7384
        }
7385
7386
        $table = Database::get_course_table(TABLE_LP_CATEGORY);
7387
        $id = (int) $id;
7388
7389
        $sql = "SELECT session_id FROM $table WHERE iid = $id";
7390
        $result = Database::query($sql);
7391
        $result = Database::fetch_array($result, 'ASSOC');
7392
7393
        if ($result) {
7394
            return (int) $result['session_id'];
7395
        }
7396
7397
        return 0;
7398
    }
7399
7400
    /**
7401
     * @param int $id
7402
     */
7403
    public static function deleteCategory($id): bool
7404
    {
7405
        $repo = Container::getLpCategoryRepository();
7406
        /** @var CLpCategory $category */
7407
        $category = $repo->find($id);
7408
        if ($category) {
7409
            $em = Database::getManager();
7410
            $lps = $category->getLps();
7411
7412
            foreach ($lps as $lp) {
7413
                $lp->setCategory(null);
7414
                $em->persist($lp);
7415
            }
7416
7417
            // Removing category.
7418
            $em->remove($category);
7419
            $em->flush();
7420
7421
            return true;
7422
        }
7423
7424
        return false;
7425
    }
7426
7427
    /**
7428
     * @param int  $courseId
7429
     * @param bool $addSelectOption
7430
     *
7431
     * @return array
7432
     */
7433
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
7434
    {
7435
        $repo = Container::getLpCategoryRepository();
7436
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId));
7437
        $items = $qb->getQuery()->getResult();
7438
7439
        $cats = [];
7440
        if ($addSelectOption) {
7441
            $cats = [get_lang('Select a category')];
7442
        }
7443
7444
        if (!empty($items)) {
7445
            foreach ($items as $cat) {
7446
                $cats[$cat->getIid()] = $cat->getName();
7447
            }
7448
        }
7449
7450
        return $cats;
7451
    }
7452
7453
    /**
7454
     * @param string $courseCode
7455
     * @param int    $lpId
7456
     * @param int    $user_id
7457
     *
7458
     * @return learnpath
7459
     */
7460
    public static function getLpFromSession($courseCode, $lpId, $user_id)
7461
    {
7462
        $debug = 0;
7463
        $learnPath = null;
7464
        $lpObject = Session::read('lpobject');
7465
7466
        $repo = Container::getLpRepository();
7467
        $lp = $repo->find($lpId);
7468
        if (null !== $lpObject) {
7469
            /** @var learnpath $learnPath */
7470
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
7471
            $learnPath->entity = $lp;
7472
            if ($debug) {
7473
                error_log('getLpFromSession: unserialize');
7474
                error_log('------getLpFromSession------');
7475
                error_log('------unserialize------');
7476
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
7477
                error_log("api_get_sessionid: ".api_get_session_id());
7478
            }
7479
        }
7480
7481
        if (!is_object($learnPath)) {
7482
            $learnPath = new learnpath($lp, api_get_course_info($courseCode), $user_id);
7483
            if ($debug) {
7484
                error_log('------getLpFromSession------');
7485
                error_log('getLpFromSession: create new learnpath');
7486
                error_log("create new LP with $courseCode - $lpId - $user_id");
7487
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
7488
                error_log("api_get_sessionid: ".api_get_session_id());
7489
            }
7490
        }
7491
7492
        return $learnPath;
7493
    }
7494
7495
    /**
7496
     * @param int $itemId
7497
     *
7498
     * @return learnpathItem|false
7499
     */
7500
    public function getItem($itemId)
7501
    {
7502
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
7503
            return $this->items[$itemId];
7504
        }
7505
7506
        return false;
7507
    }
7508
7509
    /**
7510
     * @return int
7511
     */
7512
    public function getCurrentAttempt()
7513
    {
7514
        $attempt = $this->getItem($this->get_current_item_id());
7515
        if ($attempt) {
7516
            return $attempt->get_attempt_id();
7517
        }
7518
7519
        return 0;
7520
    }
7521
7522
    /**
7523
     * @return int
7524
     */
7525
    public function getCategoryId()
7526
    {
7527
        return (int) $this->categoryId;
7528
    }
7529
7530
    /**
7531
     * Get whether this is a learning path with the possibility to subscribe
7532
     * users or not.
7533
     *
7534
     * @return int
7535
     */
7536
    public function getSubscribeUsers()
7537
    {
7538
        return $this->subscribeUsers;
7539
    }
7540
7541
    /**
7542
     * Calculate the count of stars for a user in this LP
7543
     * This calculation is based on the following rules:
7544
     * - the student gets one star when he gets to 50% of the learning path
7545
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
7546
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
7547
     * - the student gets the final star when the score for the *last* test is >= 80%.
7548
     *
7549
     * @param int $sessionId Optional. The session ID
7550
     *
7551
     * @return int The count of stars
7552
     */
7553
    public function getCalculateStars($sessionId = 0)
7554
    {
7555
        $stars = 0;
7556
        $progress = self::getProgress(
7557
            $this->lp_id,
7558
            $this->user_id,
7559
            $this->course_int_id,
7560
            $sessionId
7561
        );
7562
7563
        if ($progress >= 50) {
7564
            $stars++;
7565
        }
7566
7567
        // Calculate stars chapters evaluation
7568
        $exercisesItems = $this->getExercisesItems();
7569
7570
        if (!empty($exercisesItems)) {
7571
            $totalResult = 0;
7572
7573
            foreach ($exercisesItems as $exerciseItem) {
7574
                $exerciseResultInfo = Event::getExerciseResultsByUser(
7575
                    $this->user_id,
7576
                    $exerciseItem->path,
7577
                    $this->course_int_id,
7578
                    $sessionId,
7579
                    $this->lp_id,
7580
                    $exerciseItem->db_id
7581
                );
7582
7583
                $exerciseResultInfo = end($exerciseResultInfo);
7584
7585
                if (!$exerciseResultInfo) {
7586
                    continue;
7587
                }
7588
7589
                if (!empty($exerciseResultInfo['max_score'])) {
7590
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
7591
                } else {
7592
                    $exerciseResult = 0;
7593
                }
7594
                $totalResult += $exerciseResult;
7595
            }
7596
7597
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
7598
7599
            if ($totalExerciseAverage >= 50) {
7600
                $stars++;
7601
            }
7602
7603
            if ($totalExerciseAverage >= 80) {
7604
                $stars++;
7605
            }
7606
        }
7607
7608
        // Calculate star for final evaluation
7609
        $finalEvaluationItem = $this->getFinalEvaluationItem();
7610
7611
        if (!empty($finalEvaluationItem)) {
7612
            $evaluationResultInfo = Event::getExerciseResultsByUser(
7613
                $this->user_id,
7614
                $finalEvaluationItem->path,
7615
                $this->course_int_id,
7616
                $sessionId,
7617
                $this->lp_id,
7618
                $finalEvaluationItem->db_id
7619
            );
7620
7621
            $evaluationResultInfo = end($evaluationResultInfo);
7622
7623
            if ($evaluationResultInfo) {
7624
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
7625
                if ($evaluationResult >= 80) {
7626
                    $stars++;
7627
                }
7628
            }
7629
        }
7630
7631
        return $stars;
7632
    }
7633
7634
    /**
7635
     * Get the items of exercise type.
7636
     *
7637
     * @return array The items. Otherwise return false
7638
     */
7639
    public function getExercisesItems()
7640
    {
7641
        $exercises = [];
7642
        foreach ($this->items as $item) {
7643
            if ('quiz' !== $item->type) {
7644
                continue;
7645
            }
7646
            $exercises[] = $item;
7647
        }
7648
7649
        array_pop($exercises);
7650
7651
        return $exercises;
7652
    }
7653
7654
    /**
7655
     * Get the item of exercise type (evaluation type).
7656
     *
7657
     * @return array The final evaluation. Otherwise return false
7658
     */
7659
    public function getFinalEvaluationItem()
7660
    {
7661
        $exercises = [];
7662
        foreach ($this->items as $item) {
7663
            if (TOOL_QUIZ !== $item->type) {
7664
                continue;
7665
            }
7666
7667
            $exercises[] = $item;
7668
        }
7669
7670
        return array_pop($exercises);
7671
    }
7672
7673
    /**
7674
     * Calculate the total points achieved for the current user in this learning path.
7675
     *
7676
     * @param int $sessionId Optional. The session Id
7677
     *
7678
     * @return int
7679
     */
7680
    public function getCalculateScore($sessionId = 0)
7681
    {
7682
        // Calculate stars chapters evaluation
7683
        $exercisesItems = $this->getExercisesItems();
7684
        $finalEvaluationItem = $this->getFinalEvaluationItem();
7685
        $totalExercisesResult = 0;
7686
        $totalEvaluationResult = 0;
7687
7688
        if (false !== $exercisesItems) {
7689
            foreach ($exercisesItems as $exerciseItem) {
7690
                $exerciseResultInfo = Event::getExerciseResultsByUser(
7691
                    $this->user_id,
7692
                    $exerciseItem->path,
7693
                    $this->course_int_id,
7694
                    $sessionId,
7695
                    $this->lp_id,
7696
                    $exerciseItem->db_id
7697
                );
7698
7699
                $exerciseResultInfo = end($exerciseResultInfo);
7700
7701
                if (!$exerciseResultInfo) {
7702
                    continue;
7703
                }
7704
7705
                $totalExercisesResult += $exerciseResultInfo['score'];
7706
            }
7707
        }
7708
7709
        if (!empty($finalEvaluationItem)) {
7710
            $evaluationResultInfo = Event::getExerciseResultsByUser(
7711
                $this->user_id,
7712
                $finalEvaluationItem->path,
7713
                $this->course_int_id,
7714
                $sessionId,
7715
                $this->lp_id,
7716
                $finalEvaluationItem->db_id
7717
            );
7718
7719
            $evaluationResultInfo = end($evaluationResultInfo);
7720
7721
            if ($evaluationResultInfo) {
7722
                $totalEvaluationResult += $evaluationResultInfo['score'];
7723
            }
7724
        }
7725
7726
        return $totalExercisesResult + $totalEvaluationResult;
7727
    }
7728
7729
    /**
7730
     * Check if URL is not allowed to be show in a iframe.
7731
     *
7732
     * @param string $src
7733
     *
7734
     * @return string
7735
     */
7736
    public function fixBlockedLinks($src)
7737
    {
7738
        $urlInfo = parse_url($src);
7739
7740
        $platformProtocol = 'https';
7741
        if (false === strpos(api_get_path(WEB_CODE_PATH), 'https')) {
7742
            $platformProtocol = 'http';
7743
        }
7744
7745
        $protocolFixApplied = false;
7746
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
7747
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
7748
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
7749
7750
        if ($platformProtocol != $scheme) {
7751
            Session::write('x_frame_source', $src);
7752
            $src = 'blank.php?error=x_frames_options';
7753
            $protocolFixApplied = true;
7754
        }
7755
7756
        if (false == $protocolFixApplied) {
7757
            if (false === strpos(api_get_path(WEB_PATH), $host)) {
7758
                // Check X-Frame-Options
7759
                $ch = curl_init();
7760
                $options = [
7761
                    CURLOPT_URL => $src,
7762
                    CURLOPT_RETURNTRANSFER => true,
7763
                    CURLOPT_HEADER => true,
7764
                    CURLOPT_FOLLOWLOCATION => true,
7765
                    CURLOPT_ENCODING => "",
7766
                    CURLOPT_AUTOREFERER => true,
7767
                    CURLOPT_CONNECTTIMEOUT => 120,
7768
                    CURLOPT_TIMEOUT => 120,
7769
                    CURLOPT_MAXREDIRS => 10,
7770
                ];
7771
7772
                $proxySettings = api_get_configuration_value('proxy_settings');
7773
                if (!empty($proxySettings) &&
7774
                    isset($proxySettings['curl_setopt_array'])
7775
                ) {
7776
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
7777
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
7778
                }
7779
7780
                curl_setopt_array($ch, $options);
7781
                $response = curl_exec($ch);
7782
                $httpCode = curl_getinfo($ch);
7783
                $headers = substr($response, 0, $httpCode['header_size']);
7784
7785
                $error = false;
7786
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
7787
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
7788
                ) {
7789
                    $error = true;
7790
                }
7791
7792
                if ($error) {
7793
                    Session::write('x_frame_source', $src);
7794
                    $src = 'blank.php?error=x_frames_options';
7795
                }
7796
            }
7797
        }
7798
7799
        return $src;
7800
    }
7801
7802
    /**
7803
     * Check if this LP has a created forum in the basis course.
7804
     *
7805
     * @deprecated
7806
     *
7807
     * @return bool
7808
     */
7809
    public function lpHasForum()
7810
    {
7811
        $forumTable = Database::get_course_table(TABLE_FORUM);
7812
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
7813
7814
        $fakeFrom = "
7815
            $forumTable f
7816
            INNER JOIN $itemProperty ip
7817
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
7818
        ";
7819
7820
        $resultData = Database::select(
7821
            'COUNT(f.iid) AS qty',
7822
            $fakeFrom,
7823
            [
7824
                'where' => [
7825
                    'ip.visibility != ? AND ' => 2,
7826
                    'ip.tool = ? AND ' => TOOL_FORUM,
7827
                    'f.c_id = ? AND ' => intval($this->course_int_id),
7828
                    'f.lp_id = ?' => intval($this->lp_id),
7829
                ],
7830
            ],
7831
            'first'
7832
        );
7833
7834
        return $resultData['qty'] > 0;
7835
    }
7836
7837
    /**
7838
     * Get the forum for this learning path.
7839
     *
7840
     * @param int $sessionId
7841
     *
7842
     * @return array
7843
     */
7844
    public function getForum($sessionId = 0)
7845
    {
7846
        $repo = Container::getForumRepository();
7847
7848
        $course = api_get_course_entity();
7849
        $session = api_get_session_entity($sessionId);
7850
        $qb = $repo->getResourcesByCourse($course, $session);
7851
7852
        return $qb->getQuery()->getResult();
7853
    }
7854
7855
    /**
7856
     * Create a forum for this learning path.
7857
     *
7858
     * @return int The forum ID if was created. Otherwise return false
7859
     */
7860
    public function createForum(CForumCategory $forumCategory)
7861
    {
7862
        return store_forum(
7863
            [
7864
                'lp_id' => $this->lp_id,
7865
                'forum_title' => $this->name,
7866
                'forum_comment' => null,
7867
                'forum_category' => $forumCategory->getIid(),
7868
                'students_can_edit_group' => ['students_can_edit' => 0],
7869
                'allow_new_threads_group' => ['allow_new_threads' => 0],
7870
                'default_view_type_group' => ['default_view_type' => 'flat'],
7871
                'group_forum' => 0,
7872
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
7873
            ],
7874
            [],
7875
            true
7876
        );
7877
    }
7878
7879
    /**
7880
     * Get the LP Final Item form.
7881
     *
7882
     * @throws Exception
7883
     *
7884
     *
7885
     * @return string
7886
     */
7887
    public function getFinalItemForm()
7888
    {
7889
        $finalItem = $this->getFinalItem();
7890
        $title = '';
7891
7892
        if ($finalItem) {
7893
            $title = $finalItem->get_title();
7894
            $buttonText = get_lang('Save');
7895
            $content = $this->getSavedFinalItem();
7896
        } else {
7897
            $buttonText = get_lang('Add this document to the course');
7898
            $content = $this->getFinalItemTemplate();
7899
        }
7900
7901
        $editorConfig = [
7902
            'ToolbarSet' => 'LearningPathDocuments',
7903
            'Width' => '100%',
7904
            'Height' => '500',
7905
            'FullPage' => true,
7906
//            'CreateDocumentDir' => $relative_prefix,
7907
    //            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
7908
  //          'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
7909
        ];
7910
7911
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
7912
            'type' => 'document',
7913
            'lp_id' => $this->lp_id,
7914
        ]);
7915
7916
        $form = new FormValidator('final_item', 'POST', $url);
7917
        $form->addText('title', get_lang('Title'));
7918
        $form->addButtonSave($buttonText);
7919
        $form->addHtml(
7920
            Display::return_message(
7921
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
7922
                'normal',
7923
                false
7924
            )
7925
        );
7926
7927
        $renderer = $form->defaultRenderer();
7928
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
7929
7930
        $form->addHtmlEditor(
7931
            'content_lp_certificate',
7932
            null,
7933
            true,
7934
            false,
7935
            $editorConfig
7936
        );
7937
        $form->addHidden('action', 'add_final_item');
7938
        $form->addHidden('path', Session::read('pathItem'));
7939
        $form->addHidden('previous', $this->get_last());
7940
        $form->setDefaults(
7941
            ['title' => $title, 'content_lp_certificate' => $content]
7942
        );
7943
7944
        if ($form->validate()) {
7945
            $values = $form->exportValues();
7946
            $lastItemId = $this->getLastInFirstLevel();
7947
7948
            if (!$finalItem) {
7949
                $documentId = $this->create_document(
7950
                    $this->course_info,
7951
                    $values['content_lp_certificate'],
7952
                    $values['title']
7953
                );
7954
                $this->add_item(
7955
                    0,
7956
                    $lastItemId,
7957
                    'final_item',
7958
                    $documentId,
7959
                    $values['title'],
7960
                );
7961
7962
                Display::addFlash(
7963
                    Display::return_message(get_lang('Added'))
7964
                );
7965
            } else {
7966
                $this->edit_document($this->course_info);
7967
            }
7968
        }
7969
7970
        return $form->returnForm();
7971
    }
7972
7973
    /**
7974
     * Check if the current lp item is first, both, last or none from lp list.
7975
     *
7976
     * @param int $currentItemId
7977
     *
7978
     * @return string
7979
     */
7980
    public function isFirstOrLastItem($currentItemId)
7981
    {
7982
        $lpItemId = [];
7983
        $typeListNotToVerify = self::getChapterTypes();
7984
7985
        // Using get_toc() function instead $this->items because returns the correct order of the items
7986
        foreach ($this->get_toc() as $item) {
7987
            if (!in_array($item['type'], $typeListNotToVerify)) {
7988
                $lpItemId[] = $item['id'];
7989
            }
7990
        }
7991
7992
        $lastLpItemIndex = count($lpItemId) - 1;
7993
        $position = array_search($currentItemId, $lpItemId);
7994
7995
        switch ($position) {
7996
            case 0:
7997
                if (!$lastLpItemIndex) {
7998
                    $answer = 'both';
7999
                    break;
8000
                }
8001
8002
                $answer = 'first';
8003
                break;
8004
            case $lastLpItemIndex:
8005
                $answer = 'last';
8006
                break;
8007
            default:
8008
                $answer = 'none';
8009
        }
8010
8011
        return $answer;
8012
    }
8013
8014
    /**
8015
     * Get whether this is a learning path with the accumulated SCORM time or not.
8016
     *
8017
     * @return int
8018
     */
8019
    public function getAccumulateScormTime()
8020
    {
8021
        return $this->accumulateScormTime;
8022
    }
8023
8024
    /**
8025
     * Returns an HTML-formatted link to a resource, to incorporate directly into
8026
     * the new learning path tool.
8027
     *
8028
     * The function is a big switch on tool type.
8029
     * In each case, we query the corresponding table for information and build the link
8030
     * with that information.
8031
     *
8032
     * @author Yannick Warnier <[email protected]> - rebranding based on
8033
     * previous work (display_addedresource_link_in_learnpath())
8034
     *
8035
     * @param int $course_id      Course code
8036
     * @param int $learningPathId The learning path ID (in lp table)
8037
     * @param int $id_in_path     the unique index in the items table
8038
     * @param int $lpViewId
8039
     *
8040
     * @return string
8041
     */
8042
    public static function rl_get_resource_link_for_learnpath(
8043
        $course_id,
8044
        $learningPathId,
8045
        $id_in_path,
8046
        $lpViewId
8047
    ) {
8048
        $session_id = api_get_session_id();
8049
8050
        $learningPathId = (int) $learningPathId;
8051
        $id_in_path = (int) $id_in_path;
8052
        $lpViewId = (int) $lpViewId;
8053
8054
        $em = Database::getManager();
8055
        $lpItemRepo = $em->getRepository(CLpItem::class);
8056
8057
        /** @var CLpItem $rowItem */
8058
        $rowItem = $lpItemRepo->findOneBy([
8059
            'lp' => $learningPathId,
8060
            'iid' => $id_in_path,
8061
        ]);
8062
        $type = $rowItem->getItemType();
8063
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
8064
        $main_dir_path = api_get_path(WEB_CODE_PATH);
8065
        $link = '';
8066
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&sid='.$session_id;
8067
8068
        switch ($type) {
8069
            case 'dir':
8070
                return $main_dir_path.'lp/blank.php';
8071
            case TOOL_CALENDAR_EVENT:
8072
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
8073
            case TOOL_ANNOUNCEMENT:
8074
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
8075
            case TOOL_LINK:
8076
                $linkInfo = Link::getLinkInfo($id);
8077
                if (isset($linkInfo['url'])) {
8078
                    return $linkInfo['url'];
8079
                }
8080
8081
                return '';
8082
            case TOOL_QUIZ:
8083
                if (empty($id)) {
8084
                    return '';
8085
                }
8086
8087
                // Get the lp_item_view with the highest view_count.
8088
                $learnpathItemViewResult = $em
8089
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
8090
                    ->findBy(
8091
                        ['item' => $rowItem->getIid(), 'view' => $lpViewId],
8092
                        ['viewCount' => 'DESC'],
8093
                        1
8094
                    );
8095
                /** @var CLpItemView $learnpathItemViewData */
8096
                $learnpathItemViewData = current($learnpathItemViewResult);
8097
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getIid() : 0;
8098
8099
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
8100
                    .http_build_query([
8101
                        'lp_init' => 1,
8102
                        'learnpath_item_view_id' => $learnpathItemViewId,
8103
                        'learnpath_id' => $learningPathId,
8104
                        'learnpath_item_id' => $id_in_path,
8105
                        'exerciseId' => $id,
8106
                    ]);
8107
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
8108
                /*$TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
8109
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
8110
                $myrow = Database::fetch_array($result);
8111
                $path = $myrow['path'];
8112
8113
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
8114
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
8115
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;*/
8116
            case TOOL_FORUM:
8117
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
8118
            case TOOL_THREAD:
8119
                // forum post
8120
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
8121
                if (empty($id)) {
8122
                    return '';
8123
                }
8124
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND iid=$id";
8125
                $result = Database::query($sql);
8126
                $myrow = Database::fetch_array($result);
8127
8128
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
8129
                    .$extraParams;
8130
            case TOOL_POST:
8131
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
8132
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
8133
                $myrow = Database::fetch_array($result);
8134
8135
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
8136
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
8137
            case TOOL_READOUT_TEXT:
8138
                return api_get_path(WEB_CODE_PATH).
8139
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
8140
            case TOOL_DOCUMENT:
8141
                $repo = Container::getDocumentRepository();
8142
                $document = $repo->find($rowItem->getPath());
8143
                if ($document) {
8144
                    $params = [
8145
                        'cid' => $course_id,
8146
                        'sid' => $session_id,
8147
                    ];
8148
8149
                    return $repo->getResourceFileUrl($document, $params, UrlGeneratorInterface::ABSOLUTE_URL);
8150
                }
8151
8152
                return null;
8153
8154
                $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...
8155
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v'];
8156
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
8157
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
8158
8159
                $openmethod = 2;
8160
                $officedoc = false;
8161
                Session::write('openmethod', $openmethod);
8162
                Session::write('officedoc', $officedoc);
8163
8164
                if ($showDirectUrl) {
8165
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
8166
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
8167
                        if (Link::isPdfLink($file)) {
8168
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
8169
8170
                            return $pdfUrl;
8171
                        }
8172
                    }
8173
8174
                    return $file;
8175
                }
8176
8177
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
8178
            case TOOL_LP_FINAL_ITEM:
8179
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
8180
                    .$extraParams;
8181
            case 'assignments':
8182
                return $main_dir_path.'work/work.php?'.$extraParams;
8183
            case TOOL_DROPBOX:
8184
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
8185
            case 'introduction_text': //DEPRECATED
8186
                return '';
8187
            case TOOL_COURSE_DESCRIPTION:
8188
                return $main_dir_path.'course_description?'.$extraParams;
8189
            case TOOL_GROUP:
8190
                return $main_dir_path.'group/group.php?'.$extraParams;
8191
            case TOOL_USER:
8192
                return $main_dir_path.'user/user.php?'.$extraParams;
8193
            case TOOL_STUDENTPUBLICATION:
8194
                if (!empty($rowItem->getPath())) {
8195
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
8196
                }
8197
8198
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
8199
        }
8200
8201
        return $link;
8202
    }
8203
8204
    /**
8205
     * Gets the name of a resource (generally used in learnpath when no name is provided).
8206
     *
8207
     * @author Yannick Warnier <[email protected]>
8208
     *
8209
     * @param string $course_code    Course code
8210
     * @param int    $learningPathId
8211
     * @param int    $id_in_path     The resource ID
8212
     *
8213
     * @return string
8214
     */
8215
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
8216
    {
8217
        $_course = api_get_course_info($course_code);
8218
        if (empty($_course)) {
8219
            return '';
8220
        }
8221
        $course_id = $_course['real_id'];
8222
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8223
        $learningPathId = (int) $learningPathId;
8224
        $id_in_path = (int) $id_in_path;
8225
8226
        $sql = "SELECT item_type, title, ref
8227
                FROM $tbl_lp_item
8228
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
8229
        $res_item = Database::query($sql);
8230
8231
        if (Database::num_rows($res_item) < 1) {
8232
            return '';
8233
        }
8234
        $row_item = Database::fetch_array($res_item);
8235
        $type = strtolower($row_item['item_type']);
8236
        $id = $row_item['ref'];
8237
        $output = '';
8238
8239
        switch ($type) {
8240
            case TOOL_CALENDAR_EVENT:
8241
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
8242
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
8243
                $myrow = Database::fetch_array($result);
8244
                $output = $myrow['title'];
8245
                break;
8246
            case TOOL_ANNOUNCEMENT:
8247
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
8248
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
8249
                $myrow = Database::fetch_array($result);
8250
                $output = $myrow['title'];
8251
                break;
8252
            case TOOL_LINK:
8253
                // Doesn't take $target into account.
8254
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
8255
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
8256
                $myrow = Database::fetch_array($result);
8257
                $output = $myrow['title'];
8258
                break;
8259
            case TOOL_QUIZ:
8260
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
8261
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
8262
                $myrow = Database::fetch_array($result);
8263
                $output = $myrow['title'];
8264
                break;
8265
            case TOOL_FORUM:
8266
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
8267
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
8268
                $myrow = Database::fetch_array($result);
8269
                $output = $myrow['forum_name'];
8270
                break;
8271
            case TOOL_THREAD:
8272
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
8273
                // Grabbing the title of the post.
8274
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
8275
                $result_title = Database::query($sql_title);
8276
                $myrow_title = Database::fetch_array($result_title);
8277
                $output = $myrow_title['post_title'];
8278
                break;
8279
            case TOOL_POST:
8280
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
8281
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
8282
                $result = Database::query($sql);
8283
                $post = Database::fetch_array($result);
8284
                $output = $post['post_title'];
8285
                break;
8286
            case 'dir':
8287
            case TOOL_DOCUMENT:
8288
                $title = $row_item['title'];
8289
                $output = '-';
8290
                if (!empty($title)) {
8291
                    $output = $title;
8292
                }
8293
                break;
8294
            case 'hotpotatoes':
8295
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8296
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
8297
                $myrow = Database::fetch_array($result);
8298
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
8299
                $last = count($pathname) - 1; // Making a correct name for the link.
8300
                $filename = $pathname[$last]; // Making a correct name for the link.
8301
                $myrow['path'] = rawurlencode($myrow['path']);
8302
                $output = $filename;
8303
                break;
8304
        }
8305
8306
        return stripslashes($output);
8307
    }
8308
8309
    /**
8310
     * Get the parent names for the current item.
8311
     *
8312
     * @param int $newItemId Optional. The item ID
8313
     *
8314
     * @return array
8315
     */
8316
    public function getCurrentItemParentNames($newItemId = 0)
8317
    {
8318
        $newItemId = $newItemId ?: $this->get_current_item_id();
8319
        $return = [];
8320
        $item = $this->getItem($newItemId);
8321
        $parent = $this->getItem($item->get_parent());
8322
8323
        while ($parent) {
8324
            $return[] = $parent->get_title();
8325
            $parent = $this->getItem($parent->get_parent());
8326
        }
8327
8328
        return array_reverse($return);
8329
    }
8330
8331
    /**
8332
     * Reads and process "lp_subscription_settings" setting.
8333
     *
8334
     * @return array
8335
     */
8336
    public static function getSubscriptionSettings()
8337
    {
8338
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
8339
        if (empty($subscriptionSettings)) {
8340
            // By default allow both settings
8341
            $subscriptionSettings = [
8342
                'allow_add_users_to_lp' => true,
8343
                'allow_add_users_to_lp_category' => true,
8344
            ];
8345
        } else {
8346
            $subscriptionSettings = $subscriptionSettings['options'];
8347
        }
8348
8349
        return $subscriptionSettings;
8350
    }
8351
8352
    /**
8353
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
8354
     */
8355
    public function exportToCourseBuildFormat()
8356
    {
8357
        if (!api_is_allowed_to_edit()) {
8358
            return false;
8359
        }
8360
8361
        $courseBuilder = new CourseBuilder();
8362
        $itemList = [];
8363
        /** @var learnpathItem $item */
8364
        foreach ($this->items as $item) {
8365
            $itemList[$item->get_type()][] = $item->get_path();
8366
        }
8367
8368
        if (empty($itemList)) {
8369
            return false;
8370
        }
8371
8372
        if (isset($itemList['document'])) {
8373
            // Get parents
8374
            foreach ($itemList['document'] as $documentId) {
8375
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
8376
                if (!empty($documentInfo['parents'])) {
8377
                    foreach ($documentInfo['parents'] as $parentInfo) {
8378
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
8379
                            continue;
8380
                        }
8381
                        $itemList['document'][] = $parentInfo['iid'];
8382
                    }
8383
                }
8384
            }
8385
8386
            $courseInfo = api_get_course_info();
8387
            foreach ($itemList['document'] as $documentId) {
8388
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
8389
                $items = DocumentManager::get_resources_from_source_html(
8390
                    $documentInfo['absolute_path'],
8391
                    true,
8392
                    TOOL_DOCUMENT
8393
                );
8394
8395
                if (!empty($items)) {
8396
                    foreach ($items as $item) {
8397
                        // Get information about source url
8398
                        $url = $item[0]; // url
8399
                        $scope = $item[1]; // scope (local, remote)
8400
                        $type = $item[2]; // type (rel, abs, url)
8401
8402
                        $origParseUrl = parse_url($url);
8403
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
8404
8405
                        if ('local' === $scope) {
8406
                            if ('abs' === $type || 'rel' === $type) {
8407
                                $documentFile = strstr($realOrigPath, 'document');
8408
                                if (false !== strpos($realOrigPath, $documentFile)) {
8409
                                    $documentFile = str_replace('document', '', $documentFile);
8410
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
8411
                                    // Document found! Add it to the list
8412
                                    if ($itemDocumentId) {
8413
                                        $itemList['document'][] = $itemDocumentId;
8414
                                    }
8415
                                }
8416
                            }
8417
                        }
8418
                    }
8419
                }
8420
            }
8421
8422
            $courseBuilder->build_documents(
8423
                api_get_session_id(),
8424
                $this->get_course_int_id(),
8425
                true,
8426
                $itemList['document']
8427
            );
8428
        }
8429
8430
        if (isset($itemList['quiz'])) {
8431
            $courseBuilder->build_quizzes(
8432
                api_get_session_id(),
8433
                $this->get_course_int_id(),
8434
                true,
8435
                $itemList['quiz']
8436
            );
8437
        }
8438
8439
        /*if (!empty($itemList['thread'])) {
8440
            $postList = [];
8441
            foreach ($itemList['thread'] as $postId) {
8442
                $post = get_post_information($postId);
8443
                if ($post) {
8444
                    if (!isset($itemList['forum'])) {
8445
                        $itemList['forum'] = [];
8446
                    }
8447
                    $itemList['forum'][] = $post['forum_id'];
8448
                    $postList[] = $postId;
8449
                }
8450
            }
8451
8452
            if (!empty($postList)) {
8453
                $courseBuilder->build_forum_posts(
8454
                    $this->get_course_int_id(),
8455
                    null,
8456
                    null,
8457
                    $postList
8458
                );
8459
            }
8460
        }*/
8461
8462
        if (!empty($itemList['thread'])) {
8463
            $threadList = [];
8464
            $em = Database::getManager();
8465
            $repo = Container::getForumThreadRepository();
8466
            foreach ($itemList['thread'] as $threadId) {
8467
                /** @var CForumThread $thread */
8468
                $thread = $repo->find($threadId);
8469
                if ($thread) {
8470
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
8471
                    $threadList[] = $thread->getIid();
8472
                }
8473
            }
8474
8475
            if (!empty($threadList)) {
8476
                $courseBuilder->build_forum_topics(
8477
                    api_get_session_id(),
8478
                    $this->get_course_int_id(),
8479
                    null,
8480
                    $threadList
8481
                );
8482
            }
8483
        }
8484
8485
        $forumCategoryList = [];
8486
        if (isset($itemList['forum'])) {
8487
            foreach ($itemList['forum'] as $forumId) {
8488
                $forumInfo = get_forums($forumId);
8489
                $forumCategoryList[] = $forumInfo['forum_category'];
8490
            }
8491
        }
8492
8493
        if (!empty($forumCategoryList)) {
8494
            $courseBuilder->build_forum_category(
8495
                api_get_session_id(),
8496
                $this->get_course_int_id(),
8497
                true,
8498
                $forumCategoryList
8499
            );
8500
        }
8501
8502
        if (!empty($itemList['forum'])) {
8503
            $courseBuilder->build_forums(
8504
                api_get_session_id(),
8505
                $this->get_course_int_id(),
8506
                true,
8507
                $itemList['forum']
8508
            );
8509
        }
8510
8511
        if (isset($itemList['link'])) {
8512
            $courseBuilder->build_links(
8513
                api_get_session_id(),
8514
                $this->get_course_int_id(),
8515
                true,
8516
                $itemList['link']
8517
            );
8518
        }
8519
8520
        if (!empty($itemList['student_publication'])) {
8521
            $courseBuilder->build_works(
8522
                api_get_session_id(),
8523
                $this->get_course_int_id(),
8524
                true,
8525
                $itemList['student_publication']
8526
            );
8527
        }
8528
8529
        $courseBuilder->build_learnpaths(
8530
            api_get_session_id(),
8531
            $this->get_course_int_id(),
8532
            true,
8533
            [$this->get_id()],
8534
            false
8535
        );
8536
8537
        $courseBuilder->restoreDocumentsFromList();
8538
8539
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
8540
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
8541
        $result = DocumentManager::file_send_for_download(
8542
            $zipPath,
8543
            true,
8544
            $this->get_name().'.zip'
8545
        );
8546
8547
        if ($result) {
8548
            api_not_allowed();
8549
        }
8550
8551
        return true;
8552
    }
8553
8554
    /**
8555
     * Get whether this is a learning path with the accumulated work time or not.
8556
     *
8557
     * @return int
8558
     */
8559
    public function getAccumulateWorkTime()
8560
    {
8561
        return (int) $this->accumulateWorkTime;
8562
    }
8563
8564
    /**
8565
     * Get whether this is a learning path with the accumulated work time or not.
8566
     *
8567
     * @return int
8568
     */
8569
    public function getAccumulateWorkTimeTotalCourse()
8570
    {
8571
        $table = Database::get_course_table(TABLE_LP_MAIN);
8572
        $sql = "SELECT SUM(accumulate_work_time) AS total
8573
                FROM $table
8574
                WHERE c_id = ".$this->course_int_id;
8575
        $result = Database::query($sql);
8576
        $row = Database::fetch_array($result);
8577
8578
        return (int) $row['total'];
8579
    }
8580
8581
    /**
8582
     * @param int $lpId
8583
     * @param int $courseId
8584
     *
8585
     * @return mixed
8586
     */
8587
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
8588
    {
8589
        $lpId = (int) $lpId;
8590
        $table = Database::get_course_table(TABLE_LP_MAIN);
8591
        $sql = "SELECT accumulate_work_time
8592
                FROM $table
8593
                WHERE iid = $lpId";
8594
        $result = Database::query($sql);
8595
        $row = Database::fetch_array($result);
8596
8597
        return $row['accumulate_work_time'];
8598
    }
8599
8600
    /**
8601
     * @param int $courseId
8602
     *
8603
     * @return int
8604
     */
8605
    public static function getAccumulateWorkTimeTotal($courseId)
8606
    {
8607
        $table = Database::get_course_table(TABLE_LP_MAIN);
8608
        $courseId = (int) $courseId;
8609
        $sql = "SELECT SUM(accumulate_work_time) AS total
8610
                FROM $table
8611
                WHERE c_id = $courseId";
8612
        $result = Database::query($sql);
8613
        $row = Database::fetch_array($result);
8614
8615
        return (int) $row['total'];
8616
    }
8617
8618
    /**
8619
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
8620
     * and put the images in.
8621
     *
8622
     * @return array
8623
     */
8624
    public static function getIconSelect()
8625
    {
8626
        $theme = api_get_visual_theme();
8627
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
8628
        $icons = ['' => get_lang('Please select an option')];
8629
8630
        if (is_dir($path)) {
8631
            $finder = new Finder();
8632
            $finder->files()->in($path);
8633
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
8634
            /** @var SplFileInfo $file */
8635
            foreach ($finder as $file) {
8636
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
8637
                    $icons[$file->getFilename()] = $file->getFilename();
8638
                }
8639
            }
8640
        }
8641
8642
        return $icons;
8643
    }
8644
8645
    /**
8646
     * @param int $lpId
8647
     *
8648
     * @return string
8649
     */
8650
    public static function getSelectedIcon($lpId)
8651
    {
8652
        $extraFieldValue = new ExtraFieldValue('lp');
8653
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
8654
        $icon = '';
8655
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
8656
            $icon = $lpIcon['value'];
8657
        }
8658
8659
        return $icon;
8660
    }
8661
8662
    /**
8663
     * @param int $lpId
8664
     *
8665
     * @return string
8666
     */
8667
    public static function getSelectedIconHtml($lpId)
8668
    {
8669
        $icon = self::getSelectedIcon($lpId);
8670
8671
        if (empty($icon)) {
8672
            return '';
8673
        }
8674
8675
        $theme = api_get_visual_theme();
8676
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
8677
8678
        return Display::img($path);
8679
    }
8680
8681
    /**
8682
     * @param string $value
8683
     *
8684
     * @return string
8685
     */
8686
    public function cleanItemTitle($value)
8687
    {
8688
        $value = Security::remove_XSS(strip_tags($value));
8689
8690
        return $value;
8691
    }
8692
8693
    public function setItemTitle(FormValidator $form)
8694
    {
8695
        if (api_get_configuration_value('save_titles_as_html')) {
8696
            $form->addHtmlEditor(
8697
                'title',
8698
                get_lang('Title'),
8699
                true,
8700
                false,
8701
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
8702
            );
8703
        } else {
8704
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
8705
            $form->applyFilter('title', 'trim');
8706
            $form->applyFilter('title', 'html_filter');
8707
        }
8708
    }
8709
8710
    /**
8711
     * @return array
8712
     */
8713
    public function getItemsForForm($addParentCondition = false)
8714
    {
8715
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8716
8717
        $sql = "SELECT * FROM $tbl_lp_item
8718
                WHERE path <> 'root' AND lp_id = ".$this->lp_id;
8719
8720
        if ($addParentCondition) {
8721
            $sql .= ' AND parent_item_id IS NULL ';
8722
        }
8723
        $sql .= ' ORDER BY display_order ASC';
8724
8725
        $result = Database::query($sql);
8726
        $arrLP = [];
8727
        while ($row = Database::fetch_array($result)) {
8728
            $arrLP[] = [
8729
                'iid' => $row['iid'],
8730
                'id' => $row['iid'],
8731
                'item_type' => $row['item_type'],
8732
                'title' => $this->cleanItemTitle($row['title']),
8733
                'title_raw' => $row['title'],
8734
                'path' => $row['path'],
8735
                'description' => Security::remove_XSS($row['description']),
8736
                'parent_item_id' => $row['parent_item_id'],
8737
                'previous_item_id' => $row['previous_item_id'],
8738
                'next_item_id' => $row['next_item_id'],
8739
                'display_order' => $row['display_order'],
8740
                'max_score' => $row['max_score'],
8741
                'min_score' => $row['min_score'],
8742
                'mastery_score' => $row['mastery_score'],
8743
                'prerequisite' => $row['prerequisite'],
8744
                'max_time_allowed' => $row['max_time_allowed'],
8745
                'prerequisite_min_score' => $row['prerequisite_min_score'],
8746
                'prerequisite_max_score' => $row['prerequisite_max_score'],
8747
            ];
8748
        }
8749
8750
        return $arrLP;
8751
    }
8752
8753
    /**
8754
     * Gets whether this SCORM learning path has been marked to use the score
8755
     * as progress. Takes into account whether the learnpath matches (SCORM
8756
     * content + less than 2 items).
8757
     *
8758
     * @return bool True if the score should be used as progress, false otherwise
8759
     */
8760
    public function getUseScoreAsProgress()
8761
    {
8762
        // If not a SCORM, we don't care about the setting
8763
        if (2 != $this->get_type()) {
8764
            return false;
8765
        }
8766
        // If more than one step in the SCORM, we don't care about the setting
8767
        if ($this->get_total_items_count() > 1) {
8768
            return false;
8769
        }
8770
        $extraFieldValue = new ExtraFieldValue('lp');
8771
        $doUseScore = false;
8772
        $useScore = $extraFieldValue->get_values_by_handler_and_field_variable(
8773
            $this->get_id(),
8774
            'use_score_as_progress'
8775
        );
8776
        if (!empty($useScore) && isset($useScore['value'])) {
8777
            $doUseScore = $useScore['value'];
8778
        }
8779
8780
        return $doUseScore;
8781
    }
8782
8783
    /**
8784
     * Get the user identifier (user_id or username
8785
     * Depends on scorm_api_username_as_student_id in app/config/configuration.php.
8786
     *
8787
     * @return string User ID or username, depending on configuration setting
8788
     */
8789
    public static function getUserIdentifierForExternalServices()
8790
    {
8791
        if (api_get_configuration_value('scorm_api_username_as_student_id')) {
8792
            return api_get_user_info(api_get_user_id())['username'];
8793
        } elseif (null != api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id')) {
8794
            $extraFieldValue = new ExtraFieldValue('user');
8795
            $extrafield = $extraFieldValue->get_values_by_handler_and_field_variable(
8796
                api_get_user_id(),
8797
                api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id')
8798
            );
8799
8800
            return $extrafield['value'];
8801
        } else {
8802
            return api_get_user_id();
8803
        }
8804
    }
8805
8806
    /**
8807
     * Save the new order for learning path items.
8808
     *
8809
     * @param array $orderList A associative array with id and parent_id keys.
8810
     */
8811
    public static function sortItemByOrderList(CLpItem $rootItem, array $orderList = [], $flush = true)
8812
    {
8813
        if (empty($orderList)) {
8814
            return true;
8815
        }
8816
        $lpItemRepo = Container::getLpItemRepository();
8817
        $em = Database::getManager();
8818
        $counter = 2;
8819
        $rootItem->setDisplayOrder(1);
8820
        $rootItem->setPreviousItemId(null);
8821
        $em->persist($rootItem);
8822
        if ($flush) {
8823
            $em->flush();
8824
        }
8825
8826
        foreach ($orderList as $item) {
8827
            $itemId = $item->id ?? 0;
8828
            if (empty($itemId)) {
8829
                continue;
8830
            }
8831
            $parentId = $item->parent_id ?? 0;
8832
            $parent = $rootItem;
8833
            if (!empty($parentId)) {
8834
                $parentExists = $lpItemRepo->find($parentId);
8835
                if (null !== $parentExists) {
8836
                    $parent = $parentExists;
8837
                }
8838
            }
8839
8840
            /** @var CLpItem $itemEntity */
8841
            $itemEntity = $lpItemRepo->find($itemId);
8842
            $itemEntity->setParent($parent);
8843
            $previousId = (int) $itemEntity->getPreviousItemId();
8844
            //if (0 === $previousId) {
8845
                $itemEntity->setPreviousItemId(null);
8846
            //}
8847
8848
            $nextId = (int) $itemEntity->getNextItemId();
8849
            //if (0 === $nextId) {
8850
                $itemEntity->setNextItemId(null);
8851
            //}
8852
8853
8854
            $itemEntity->setDisplayOrder($counter);
8855
            $em->persist($itemEntity);
8856
            if ($flush) {
8857
                $em->flush();
8858
            }
8859
            $counter++;
8860
        }
8861
8862
        $em->flush();
8863
        $lpItemRepo->recoverNode($rootItem, 'displayOrder');
8864
        $em->persist($rootItem);
8865
        if ($flush) {
8866
            $em->flush();
8867
        }
8868
        //$em->flush();
8869
        //var_dump($lpItemRepo->verify());
8870
8871
        return true;
8872
    }
8873
8874
    /**
8875
     * Get the depth level of LP item.
8876
     *
8877
     * @param array $items
8878
     * @param int   $currentItemId
8879
     *
8880
     * @return int
8881
     */
8882
    private static function get_level_for_item($items, $currentItemId)
8883
    {
8884
        $parentItemId = 0;
8885
        if (isset($items[$currentItemId])) {
8886
            $parentItemId = $items[$currentItemId]->parent;
8887
        }
8888
8889
        if (0 == $parentItemId) {
8890
            return 0;
8891
        }
8892
8893
        return self::get_level_for_item($items, $parentItemId) + 1;
8894
    }
8895
8896
    /**
8897
     * Generate the link for a learnpath category as course tool.
8898
     *
8899
     * @param int $categoryId
8900
     *
8901
     * @return string
8902
     */
8903
    private static function getCategoryLinkForTool($categoryId)
8904
    {
8905
        $categoryId = (int) $categoryId;
8906
        return 'lp/lp_controller.php?'.api_get_cidreq().'&'
8907
            .http_build_query(
8908
                [
8909
                    'action' => 'view_category',
8910
                    'id' => $categoryId,
8911
                ]
8912
            );
8913
    }
8914
8915
    /**
8916
     * Check and obtain the lp final item if exist.
8917
     *
8918
     * @return learnpathItem
8919
     */
8920
    private function getFinalItem()
8921
    {
8922
        if (empty($this->items)) {
8923
            return null;
8924
        }
8925
8926
        foreach ($this->items as $item) {
8927
            if ('final_item' !== $item->type) {
8928
                continue;
8929
            }
8930
8931
            return $item;
8932
        }
8933
    }
8934
8935
    /**
8936
     * Get the LP Final Item Template.
8937
     *
8938
     * @return string
8939
     */
8940
    private function getFinalItemTemplate()
8941
    {
8942
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
8943
    }
8944
8945
    /**
8946
     * Get the LP Final Item Url.
8947
     *
8948
     * @return string
8949
     */
8950
    private function getSavedFinalItem()
8951
    {
8952
        $finalItem = $this->getFinalItem();
8953
8954
        $repo = Container::getDocumentRepository();
8955
        /** @var CDocument $document */
8956
        $document = $repo->find($finalItem->path);
8957
8958
        if ($document && $document->getResourceNode()->hasResourceFile()) {
8959
            return $repo->getResourceFileContent($document);
8960
        }
8961
8962
        return '';
8963
    }
8964
}
8965