Passed
Push — master ( dedfe2...0b77d3 )
by Julito
08:10
created

learnpath   F

Complexity

Total Complexity 1306

Size/Duplication

Total Lines 11523
Duplicated Lines 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
eloc 5567
c 3
b 1
f 0
dl 0
loc 11523
rs 0.8
wmc 1306

200 Methods

Rating   Name   Duplication   Size   Complexity  
A getCountCategories() 0 10 2
A deleteCategory() 0 22 3
A getProgressBar() 0 5 1
B get_iv_objectives_array() 0 38 6
A get_total_items_count() 0 3 1
A get_first_item_id() 0 8 2
A get_course_int_id() 0 3 1
B edit_item() 0 98 7
A get_last() 0 10 2
A get_type() 0 8 3
A get_progress_bar() 0 9 1
A set_course_int_id() 0 3 1
A get_type_static() 0 16 3
A get_items_status_list() 0 10 2
A get_theme() 0 7 2
B add_item() 0 117 8
A get_previous_index() 0 16 5
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
F first() 0 71 20
A get_items_details_as_js() 0 8 2
A get_progress_bar_mode() 0 7 2
A get_current_item_id() 0 8 2
A getLastInFirstLevel() 0 13 2
A getChapterTypes() 0 4 1
F __construct() 0 252 39
B get_iv_interactions_array() 0 54 8
A close() 0 13 2
A getNameNoTags() 0 3 1
C get_mediaplayer() 0 79 13
A getProgressFromLpList() 0 32 4
F autocomplete_parents() 0 101 17
A get_interactions_count_from_db() 0 16 2
B get_scorm_prereq_string() 0 73 11
B get_scorm_xml_node() 0 19 7
B edit_item_prereq() 0 36 7
A get_id() 0 7 2
A get_next_item_id() 0 10 3
F is_lp_visible_for_student() 0 102 22
A get_flat_ordered_items_list() 0 59 5
A getProgress() 0 23 2
A get_complete_items_count() 0 24 5
A get_objectives_count_from_db() 0 16 2
B delete_item() 0 48 6
A getTotalItemsCountWithoutDirs() 0 11 3
C add_lp() 0 109 11
C getListArrayToc() 0 77 11
A getCourseCode() 0 3 1
B get_navigation_bar() 0 72 9
B get_next_index() 0 23 7
D getPackageType() 0 90 20
A get_common_index_terms_by_prefix() 0 16 3
A get_lp_session_id() 0 7 2
A get_previous_item_id() 0 5 1
A get_toc() 0 19 2
B delete() 0 102 8
A get_name() 0 7 2
A getStatusCSSClassName() 0 7 2
A delete_children_items() 0 22 4
B update_default_view_mode() 0 33 6
A next() 0 22 5
A togglePublish() 0 22 4
A getDropElementHtml() 0 12 2
D display_edit_item() 0 75 20
A set_autolaunch() 0 27 2
A display_thread_form() 0 13 1
B getCalculateScore() 0 47 6
F save_last() 0 80 21
A get_update_queue() 0 3 1
A copy() 0 26 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 tree_array() 0 4 1
A createCategory() 0 15 1
B move_down() 0 54 8
B get_exercises() 0 137 4
C fixBlockedLinks() 0 64 11
A open() 0 9 1
A set_jslib() 0 15 2
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
A updateCategory() 0 9 2
C start_current_item() 0 42 16
A displayItemMenu() 0 89 5
B set_current_item() 0 33 10
A getUserIdentifierForExternalServices() 0 14 3
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 display_link_form() 0 22 2
A getForum() 0 9 1
A getExercisesItems() 0 13 3
B getFinalItemForm() 0 86 4
A getSelectedIcon() 0 10 3
C getBuildTree() 0 195 11
B sortItemByOrderList() 0 108 8
F createReadOutText() 0 121 27
A moveDownCategory() 0 11 2
A sort_tree_array() 0 12 3
F scormExport() 0 973 114
A getAccumulateWorkTimePrerequisite() 0 11 1
A getSelectedIconHtml() 0 12 2
A display_item_form() 0 14 1
A select_previous_item_id() 0 22 1
A getItemsForForm() 0 38 3
A set_seriousgame_mode() 0 15 3
A returnLpItemList() 0 15 2
A getItem() 0 7 3
B updateList() 0 43 7
A getCategoryId() 0 3 1
B create_tree_array() 0 37 11
B get_links() 0 133 7
B set_terms_by_prefix() 0 68 10
A get_user_id() 0 7 2
A set_use_max_score() 0 12 1
A create_path() 0 14 5
A set_previous_item() 0 6 2
A getCurrentBuildingModeURL() 0 11 5
A display_document() 0 26 3
A save_current() 0 32 6
C rl_get_resource_name() 0 92 14
A displayNewSectionForm() 0 18 1
A getSavedFinalItem() 0 13 3
B restart() 0 38 6
A cleanItemTitle() 0 5 1
A format_scorm_type_item() 0 3 1
A getCurrentItemParentNames() 0 13 3
A getSubscribeUsers() 0 3 1
A set_attempt_mode() 0 32 5
A update_display_order() 0 30 5
A generate_lp_folder() 0 36 4
A getAccumulateWorkTimeTotalCourse() 0 10 1
A display_quiz_form() 0 13 1
A moveUpCategory() 0 11 2
C display_item() 0 81 14
B overview() 0 59 8
B move_up() 0 51 8
D display_item_prerequisites_form() 0 210 15
B print_recursive() 0 40 10
C scorm_export_to_pdf() 0 71 12
B isFirstOrLastItem() 0 32 6
C display_move_item() 0 55 12
C getCalculateStars() 0 79 12
A has_audio() 0 11 3
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
B get_view() 0 53 7
A getCategoryLinkForTool() 0 8 1
A getSubscriptionSettings() 0 14 2
B verify_document_size() 0 25 8
B showBuildSideBar() 0 451 6
B get_documents() 0 104 2
C get_forums() 0 139 9
A edit_document() 0 16 4
D move_item() 0 125 18
A getCurrentAttempt() 0 8 2
A display_forum_form() 0 16 2
A update_scorm_debug() 0 15 3
A getUseScoreAsProgress() 0 21 5
A toggleCategoryVisibility() 0 19 3
A getFinalItem() 0 12 4
A displayResources() 0 55 2
A update_reinit() 0 16 3
A getFinalItemTemplate() 0 3 1
A get_extension() 0 5 1
F rl_get_resource_link_for_learnpath() 0 157 30
A get_view_id() 0 7 2
A getAccumulateWorkTimeTotal() 0 11 1
A getLpFromSession() 0 33 5
A toggleCategoryPublish() 0 25 4
A generate_learning_path_folder() 0 16 2
A getCategories() 0 9 1
B get_attempt_mode() 0 21 9
A toggleVisibility() 0 18 3
A return_new_tree() 0 35 2
A getIconSelect() 0 19 4
F exportToCourseBuildFormat() 0 197 31
C categoryIsVisibleForStudent() 0 83 16
A get_level_for_item() 0 12 3
A clearPrerequisites() 0 14 1
A get_student_publications() 0 53 3
A lpHasForum() 0 26 1
A display_student_publication_form() 0 12 1
B save_item() 0 47 9
F processBuildMenuElements() 0 439 51

How to fix   Complexity   

Complex Class

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

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

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

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\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\Filesystem\Filesystem;
28
use Symfony\Component\Finder\Finder;
29
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
30
31
/**
32
 * Class learnpath
33
 * This class defines the parent attributes and methods for Chamilo learnpaths
34
 * and SCORM learnpaths. It is used by the scorm class.
35
 *
36
 * @todo decouple class
37
 *
38
 * @author  Yannick Warnier <[email protected]>
39
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
40
 */
41
class learnpath
42
{
43
    public const MAX_LP_ITEM_TITLE_LENGTH = 36;
44
    public const STATUS_CSS_CLASS_NAME = [
45
        'not attempted' => 'scorm_not_attempted',
46
        'incomplete' => 'scorm_not_attempted',
47
        'failed' => 'scorm_failed',
48
        'completed' => 'scorm_completed',
49
        'passed' => 'scorm_completed',
50
        'succeeded' => 'scorm_completed',
51
        'browsed' => 'scorm_completed',
52
    ];
53
54
    public $attempt = 0; // The number for the current ID view.
55
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
56
    public $current; // Id of the current item the user is viewing.
57
    public $current_score; // The score of the current item.
58
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
59
    public $current_time_stop; // The time the user closed this resource.
60
    public $default_status = 'not attempted';
61
    public $encoding = 'UTF-8';
62
    public $error = '';
63
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
64
    public $index; // The index of the active learnpath_item in $ordered_items array.
65
    /** @var learnpathItem[] */
66
    public $items = [];
67
    public $last; // item_id of last item viewed in the learning path.
68
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
69
    public $license; // Which license this course has been given - not used yet on 20060522.
70
    public $lp_id; // DB iid for this learnpath.
71
    public $lp_view_id; // DB ID for lp_view
72
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
73
    public $message = '';
74
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
75
    public $name; // Learnpath name (they generally have one).
76
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
77
    public $path = ''; // Path inside the scorm directory (if scorm).
78
    public $theme; // The current theme of the learning path.
79
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
80
    public $accumulateWorkTime; // The min time of learnpath
81
82
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
83
    public $prevent_reinit = 1;
84
85
    // Describes the mode of progress bar display.
86
    public $seriousgame_mode = 0;
87
    public $progress_bar_mode = '%';
88
89
    // Percentage progress as saved in the db.
90
    public $progress_db = 0;
91
    public $proximity; // Wether the content is distant or local or unknown.
92
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
93
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
94
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
95
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
96
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
97
    public $user_id; //ID of the user that is viewing/using the course
98
    public $update_queue = [];
99
    public $scorm_debug = 0;
100
    public $arrMenu = []; // Array for the menu items.
101
    public $debug = 0; // Logging level.
102
    public $lp_session_id = 0;
103
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
104
    public $prerequisite = 0;
105
    public $use_max_score = 1; // 1 or 0
106
    public $subscribeUsers = 0; // Subscribe users or not
107
    public $created_on = '';
108
    public $modified_on = '';
109
    public $publicated_on = '';
110
    public $expired_on = '';
111
    public $ref;
112
    public $course_int_id;
113
    public $course_info;
114
    public $categoryId;
115
    public $scormUrl;
116
    public $entity;
117
118
    public function __construct(CLp $entity = null, $course_info, $user_id)
119
    {
120
        $debug = $this->debug;
121
        $user_id = (int) $user_id;
122
        $this->encoding = api_get_system_encoding();
123
        $lp_id = 0;
124
        if (null !== $entity) {
125
            $lp_id = (int) $entity->getIid();
126
        }
127
        $course_info = empty($course_info) ? api_get_course_info() : $course_info;
128
        $course_id = (int) $course_info['real_id'];
129
        $this->course_info = $course_info;
130
        $this->set_course_int_id($course_id);
131
        if (empty($lp_id) || empty($course_id)) {
132
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
133
        } else {
134
            $this->entity = $entity;
135
            $this->lp_id = $lp_id;
136
            $this->type = $entity->getLpType();
137
            $this->name = stripslashes($entity->getName());
138
            $this->proximity = $entity->getContentLocal();
139
            $this->theme = $entity->getTheme();
140
            $this->maker = $entity->getContentLocal();
141
            $this->prevent_reinit = $entity->getPreventReinit();
142
            $this->seriousgame_mode = $entity->getSeriousgameMode();
143
            $this->license = $entity->getContentLicense();
144
            $this->scorm_debug = $entity->getDebug();
145
            $this->js_lib = $entity->getJsLib();
146
            $this->path = $entity->getPath();
147
            $this->author = $entity->getAuthor();
148
            $this->hide_toc_frame = $entity->getHideTocFrame();
149
            //$this->lp_session_id = $entity->getSessionId();
150
            $this->use_max_score = $entity->getUseMaxScore();
151
            $this->subscribeUsers = $entity->getSubscribeUsers();
152
            $this->created_on = $entity->getCreatedOn()->format('Y-m-d H:i:s');
153
            $this->modified_on = $entity->getModifiedOn()->format('Y-m-d H:i:s');
154
            $this->ref = $entity->getRef();
155
            $this->categoryId = 0;
156
            if ($entity->getCategory()) {
157
                $this->categoryId = $entity->getCategory()->getIid();
158
            }
159
160
            if ($entity->hasAsset()) {
161
                $asset = $entity->getAsset();
162
                $this->scormUrl = Container::getAssetRepository()->getAssetUrl($asset).'/';
163
            }
164
165
            $this->accumulateScormTime = $entity->getAccumulateWorkTime();
166
167
            if (!empty($entity->getPublicatedOn())) {
168
                $this->publicated_on = $entity->getPublicatedOn()->format('Y-m-d H:i:s');
169
            }
170
171
            if (!empty($entity->getExpiredOn())) {
172
                $this->expired_on = $entity->getExpiredOn()->format('Y-m-d H:i:s');
173
            }
174
            if (2 == $this->type) {
175
                if (1 == $entity->getForceCommit()) {
176
                    $this->force_commit = true;
177
                }
178
            }
179
            $this->mode = $entity->getDefaultViewMod();
180
181
            // Check user ID.
182
            if (empty($user_id)) {
183
                $this->error = 'User ID is empty';
184
            } else {
185
                $this->user_id = $user_id;
186
            }
187
188
            // End of variables checking.
189
            $session_id = api_get_session_id();
190
            //  Get the session condition for learning paths of the base + session.
191
            $session = api_get_session_condition($session_id);
192
            // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
193
            $lp_table = Database::get_course_table(TABLE_LP_VIEW);
194
195
            // Selecting by view_count descending allows to get the highest view_count first.
196
            $sql = "SELECT * FROM $lp_table
197
                    WHERE
198
                        c_id = $course_id AND
199
                        lp_id = $lp_id AND
200
                        user_id = $user_id
201
                        $session
202
                    ORDER BY view_count DESC";
203
            $res = Database::query($sql);
204
205
            if (Database::num_rows($res) > 0) {
206
                $row = Database::fetch_array($res);
207
                $this->attempt = $row['view_count'];
208
                $this->lp_view_id = $row['iid'];
209
                $this->last_item_seen = $row['last_item'];
210
                $this->progress_db = $row['progress'];
211
                $this->lp_view_session_id = $row['session_id'];
212
            } elseif (!api_is_invitee()) {
213
                $this->attempt = 1;
214
                $params = [
215
                    'c_id' => $course_id,
216
                    'lp_id' => $lp_id,
217
                    'user_id' => $user_id,
218
                    'view_count' => 1,
219
                    //'session_id' => $session_id,
220
                    'last_item' => 0,
221
                ];
222
                if (!empty($session_id)) {
223
                    $params['session_id'] = $session_id;
224
                }
225
                $this->last_item_seen = 0;
226
                $this->lp_view_session_id = $session_id;
227
                $this->lp_view_id = Database::insert($lp_table, $params);
228
            }
229
230
            $criteria = Criteria::create()
231
                ->orderBy(
232
                    [
233
                        'parent' => Criteria::ASC,
234
                        'displayOrder' => Criteria::ASC,
235
                    ]
236
                );
237
            $items = $this->entity->getItems()->matching($criteria);
238
            $lp_item_id_list = [];
239
            foreach ($items as $item) {
240
                $itemId = $item->getIid();
241
                $lp_item_id_list[] = $itemId;
242
                switch ($this->type) {
243
                    case 3: //aicc
244
                        $oItem = new aiccItem('db', $itemId, $course_id);
245
                        if (is_object($oItem)) {
246
                            $my_item_id = $oItem->get_id();
247
                            $oItem->set_lp_view($this->lp_view_id, $course_id);
248
                            $oItem->set_prevent_reinit($this->prevent_reinit);
249
                            // Don't use reference here as the next loop will make the pointed object change.
250
                            $this->items[$my_item_id] = $oItem;
251
                            $this->refs_list[$oItem->ref] = $my_item_id;
252
                        }
253
                        break;
254
                    case 2:
255
                        $oItem = new scormItem('db', $itemId, $course_id);
256
                        if (is_object($oItem)) {
257
                            $my_item_id = $oItem->get_id();
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[$my_item_id] = $oItem;
262
                            $this->refs_list[$oItem->ref] = $my_item_id;
263
                        }
264
                        break;
265
                    case 1:
266
                    default:
267
                        $oItem = new learnpathItem($itemId, $course_id, $item);
268
                        if (is_object($oItem)) {
269
                            $my_item_id = $oItem->get_id();
270
                            // Moved down to when we are sure the item_view exists.
271
                            //$oItem->set_lp_view($this->lp_view_id);
272
                            $oItem->set_prevent_reinit($this->prevent_reinit);
273
                            // Don't use reference here as the next loop will make the pointed object change.
274
                            $this->items[$my_item_id] = $oItem;
275
                            $this->refs_list[$my_item_id] = $my_item_id;
276
                        }
277
                        break;
278
                }
279
280
                // Setting the object level with variable $this->items[$i][parent]
281
                foreach ($this->items as $itemLPObject) {
282
                    $level = self::get_level_for_item($this->items, $itemLPObject->db_id);
283
                    $itemLPObject->level = $level;
284
                }
285
286
                // Setting the view in the item object.
287
                if (is_object($this->items[$itemId])) {
288
                    $this->items[$itemId]->set_lp_view($this->lp_view_id, $course_id);
289
                    if (TOOL_HOTPOTATOES == $this->items[$itemId]->get_type()) {
290
                        $this->items[$itemId]->current_start_time = 0;
291
                        $this->items[$itemId]->current_stop_time = 0;
292
                    }
293
                }
294
            }
295
296
            if (!empty($lp_item_id_list)) {
297
                $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
298
                if (!empty($lp_item_id_list_to_string)) {
299
                    // Get last viewing vars.
300
                    $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
301
                    // This query should only return one or zero result.
302
                    $sql = "SELECT lp_item_id, status
303
                            FROM $itemViewTable
304
                            WHERE
305
                                lp_view_id = ".$this->get_view_id()." AND
306
                                lp_item_id IN ('".$lp_item_id_list_to_string."')
307
                            ORDER BY view_count DESC ";
308
                    $status_list = [];
309
                    $res = Database::query($sql);
310
                    while ($row = Database:: fetch_array($res)) {
311
                        $status_list[$row['lp_item_id']] = $row['status'];
312
                    }
313
314
                    foreach ($lp_item_id_list as $item_id) {
315
                        if (isset($status_list[$item_id])) {
316
                            $status = $status_list[$item_id];
317
                            if (is_object($this->items[$item_id])) {
318
                                $this->items[$item_id]->set_status($status);
319
                                if (empty($status)) {
320
                                    $this->items[$item_id]->set_status(
321
                                        $this->default_status
322
                                    );
323
                                }
324
                            }
325
                        } else {
326
                            if (!api_is_invitee()) {
327
                                if (is_object($this->items[$item_id])) {
328
                                    $this->items[$item_id]->set_status(
329
                                        $this->default_status
330
                                    );
331
                                }
332
333
                                if (!empty($this->lp_view_id)) {
334
                                    // Add that row to the lp_item_view table so that
335
                                    // we have something to show in the stats page.
336
                                    $params = [
337
                                        'lp_item_id' => $item_id,
338
                                        'lp_view_id' => $this->lp_view_id,
339
                                        'view_count' => 1,
340
                                        'status' => 'not attempted',
341
                                        'start_time' => time(),
342
                                        'total_time' => 0,
343
                                        'score' => 0,
344
                                    ];
345
                                    Database::insert($itemViewTable, $params);
346
347
                                    $this->items[$item_id]->set_lp_view(
348
                                        $this->lp_view_id,
349
                                        $course_id
350
                                    );
351
                                }
352
                            }
353
                        }
354
                    }
355
                }
356
            }
357
358
            $this->ordered_items = self::get_flat_ordered_items_list($this->entity, null);
359
            $this->max_ordered_items = 0;
360
            foreach ($this->ordered_items as $index => $dummy) {
361
                if ($index > $this->max_ordered_items && !empty($dummy)) {
362
                    $this->max_ordered_items = $index;
363
                }
364
            }
365
            // TODO: Define the current item better.
366
            $this->first();
367
            if ($debug) {
368
                error_log('lp_view_session_id '.$this->lp_view_session_id);
369
                error_log('End of learnpath constructor for learnpath '.$this->get_id());
370
            }
371
        }
372
    }
373
374
    /**
375
     * @return string
376
     */
377
    public function getCourseCode()
378
    {
379
        return $this->cc;
380
    }
381
382
    /**
383
     * @return int
384
     */
385
    public function get_course_int_id()
386
    {
387
        return $this->course_int_id ?? api_get_course_int_id();
388
    }
389
390
    /**
391
     * @param $course_id
392
     *
393
     * @return int
394
     */
395
    public function set_course_int_id($course_id)
396
    {
397
        return $this->course_int_id = (int) $course_id;
398
    }
399
400
    /**
401
     * Function rewritten based on old_add_item() from Yannick Warnier.
402
     * Due the fact that users can decide where the item should come, I had to overlook this function and
403
     * I found it better to rewrite it. Old function is still available.
404
     * Added also the possibility to add a description.
405
     *
406
     * @param CLpItem $parent
407
     * @param int     $previousId
408
     * @param string  $type
409
     * @param int     $id resource ID (ref)
410
     * @param string  $title
411
     * @param string  $description
412
     * @param int     $prerequisites
413
     * @param int     $max_time_allowed
414
     * @param int     $userId
415
     *
416
     * @return int
417
     */
418
    public function add_item(
419
        ?CLpItem $parent,
420
        $previousId,
421
        $type,
422
        $id,
423
        $title,
424
        $description = '',
425
        $prerequisites = 0,
426
        $max_time_allowed = 0
427
    ) {
428
        $type = empty($type) ? 'dir' : $type;
429
        $course_id = $this->course_info['real_id'];
430
        if (empty($course_id)) {
431
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
432
            $this->course_info = api_get_course_info($this->cc);
433
            $course_id = $this->course_info['real_id'];
434
        }
435
        $id = (int) $id;
436
        $max_time_allowed = (int) $max_time_allowed;
437
        if (empty($max_time_allowed)) {
438
            $max_time_allowed = 0;
439
        }
440
        $lpId = $this->get_id();
441
        $max_score = 100;
442
        if ('quiz' === $type && $id) {
443
            $sql = 'SELECT SUM(ponderation)
444
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as q
445
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
446
                    ON q.iid = quiz_rel_question.question_id
447
                    WHERE
448
                        quiz_rel_question.quiz_id = '.$id;
449
            $rsQuiz = Database::query($sql);
450
            $max_score = Database::result($rsQuiz, 0, 0);
451
452
            // Disabling the exercise if we add it inside a LP
453
            $exercise = new Exercise($course_id);
454
            $exercise->read($id);
455
            $exercise->disable();
456
            $exercise->save();
457
            $title = $exercise->get_formated_title();
458
        }
459
460
        $lpItem = new CLpItem();
461
        $lpItem
462
            ->setTitle($title)
463
            ->setDescription($description)
464
            ->setPath($id)
465
            ->setLp($this->entity)
466
            ->setItemType($type)
467
            ->setMaxScore($max_score)
468
            ->setMaxTimeAllowed($max_time_allowed)
469
            ->setPrerequisite($prerequisites)
470
            //->setDisplayOrder($display_order + 1)
471
            //->setNextItemId((int) $next)
472
            //->setPreviousItemId($previous)
473
        ;
474
475
        if (!empty($parent))  {
476
            $lpItem->setParent($parent);
477
        }
478
        $em = Database::getManager();
479
        $em->persist($lpItem);
480
        $em->flush();
481
482
        $new_item_id = $lpItem->getIid();
483
        if ($new_item_id) {
484
            // @todo fix upload audio.
485
            // Upload audio.
486
            /*if (!empty($_FILES['mp3']['name'])) {
487
                // Create the audio folder if it does not exist yet.
488
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
489
                if (!is_dir($filepath.'audio')) {
490
                    mkdir(
491
                        $filepath.'audio',
492
                        api_get_permissions_for_new_directories()
493
                    );
494
                    DocumentManager::addDocument(
495
                        $_course,
496
                        '/audio',
497
                        'folder',
498
                        0,
499
                        'audio',
500
                        '',
501
                        0,
502
                        true,
503
                        null,
504
                        $sessionId,
505
                        $userId
506
                    );
507
                }
508
509
                $file_path = handle_uploaded_document(
510
                    $_course,
511
                    $_FILES['mp3'],
512
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
513
                    '/audio',
514
                    $userId,
515
                    '',
516
                    '',
517
                    '',
518
                    '',
519
                    false
520
                );
521
522
                // Getting the filename only.
523
                $file_components = explode('/', $file_path);
524
                $file = $file_components[count($file_components) - 1];
525
526
                // Store the mp3 file in the lp_item table.
527
                $sql = "UPDATE $tbl_lp_item SET
528
                          audio = '".Database::escape_string($file)."'
529
                        WHERE iid = '".intval($new_item_id)."'";
530
                Database::query($sql);
531
            }*/
532
        }
533
534
        return $new_item_id;
535
    }
536
537
    /**
538
     * Static admin function allowing addition of a learnpath to a course.
539
     *
540
     * @param string $courseCode
541
     * @param string $name
542
     * @param string $description
543
     * @param string $learnpath
544
     * @param string $origin
545
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
546
     * @param string $publicated_on
547
     * @param string $expired_on
548
     * @param int    $categoryId
549
     * @param int    $userId
550
     *
551
     * @return CLp
552
     */
553
    public static function add_lp(
554
        $courseCode,
555
        $name,
556
        $description = '',
557
        $learnpath = 'guess',
558
        $origin = 'zip',
559
        $zipname = '',
560
        $publicated_on = '',
561
        $expired_on = '',
562
        $categoryId = 0,
563
        $userId = 0
564
    ) {
565
        global $charset;
566
567
        if (!empty($courseCode)) {
568
            $courseInfo = api_get_course_info($courseCode);
569
            $course_id = $courseInfo['real_id'];
570
        } else {
571
            $course_id = api_get_course_int_id();
572
            $courseInfo = api_get_course_info();
573
        }
574
575
        $categoryId = (int) $categoryId;
576
577
        if (empty($publicated_on)) {
578
            $publicated_on = null;
579
        } else {
580
            $publicated_on = api_get_utc_datetime($publicated_on, true, true);
581
        }
582
583
        if (empty($expired_on)) {
584
            $expired_on = null;
585
        } else {
586
            $expired_on = api_get_utc_datetime($expired_on, true, true);
587
        }
588
589
        /*$check_name = "SELECT * FROM $tbl_lp
590
                       WHERE c_id = $course_id AND name = '".Database::escape_string($name)."'";
591
        $res_name = Database::query($check_name);
592
593
        while (Database::num_rows($res_name)) {
594
            // There is already one such name, update the current one a bit.
595
            $i++;
596
            $name = $name.' - '.$i;
597
            $check_name = "SELECT * FROM $tbl_lp
598
                           WHERE c_id = $course_id AND name = '".Database::escape_string($name)."' ";
599
            $res_name = Database::query($check_name);
600
        }*/
601
        // New name does not exist yet; keep it.
602
        // Escape description.
603
        // Kevin: added htmlentities().
604
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
605
        $type = 1;
606
        switch ($learnpath) {
607
            case 'guess':
608
            case 'aicc':
609
                break;
610
            case 'dokeos':
611
            case 'chamilo':
612
                $type = 1;
613
                break;
614
        }
615
616
        $id = null;
617
        $sessionEntity = api_get_session_entity();
618
        $courseEntity = api_get_course_entity($courseInfo['real_id']);
619
        $lp = null;
620
        switch ($origin) {
621
            case 'zip':
622
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
623
                break;
624
            case 'manual':
625
            default:
626
                /*$get_max = "SELECT MAX(display_order)
627
                            FROM $tbl_lp WHERE c_id = $course_id";
628
                $res_max = Database::query($get_max);
629
                if (Database::num_rows($res_max) < 1) {
630
                    $dsp = 1;
631
                } else {
632
                    $row = Database::fetch_array($res_max);
633
                    $dsp = $row[0] + 1;
634
                }*/
635
636
                $dsp = 1;
637
                $category = null;
638
                if (!empty($categoryId)) {
639
                    $category = Container::getLpCategoryRepository()->find($categoryId);
640
                }
641
642
                $lp = new CLp();
643
                $lp
644
                    ->setLpType($type)
645
                    ->setName($name)
646
                    ->setDescription($description)
647
                    ->setDisplayOrder($dsp)
648
                    ->setCategory($category)
649
                    ->setPublicatedOn($publicated_on)
650
                    ->setExpiredOn($expired_on)
651
                    ->setParent($courseEntity)
652
                    ->addCourseLink($courseEntity, $sessionEntity)
653
                ;
654
655
                $em = Database::getManager();
656
                $em->persist($lp);
657
                $em->flush();
658
                break;
659
        }
660
661
        return $lp;
662
    }
663
664
    /**
665
     * Auto completes the parents of an item in case it's been completed or passed.
666
     *
667
     * @param int $item Optional ID of the item from which to look for parents
668
     */
669
    public function autocomplete_parents($item)
670
    {
671
        $debug = $this->debug;
672
673
        if (empty($item)) {
674
            $item = $this->current;
675
        }
676
677
        $currentItem = $this->getItem($item);
678
        if ($currentItem) {
679
            $parent_id = $currentItem->get_parent();
680
            $parent = $this->getItem($parent_id);
681
            if ($parent) {
682
                // if $item points to an object and there is a parent.
683
                if ($debug) {
684
                    error_log(
685
                        'Autocompleting parent of item '.$item.' '.
686
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
687
                        0
688
                    );
689
                }
690
691
                // New experiment including failed and browsed in completed status.
692
                //$current_status = $currentItem->get_status();
693
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
694
                // Fixes chapter auto complete
695
                if (true) {
696
                    // If the current item is completed or passes or succeeded.
697
                    $updateParentStatus = true;
698
                    if ($debug) {
699
                        error_log('Status of current item is alright');
700
                    }
701
702
                    foreach ($parent->get_children() as $childItemId) {
703
                        $childItem = $this->getItem($childItemId);
704
705
                        // If children was not set try to get the info
706
                        if (empty($childItem->db_item_view_id)) {
707
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
708
                        }
709
710
                        // Check all his brothers (parent's children) for completion status.
711
                        if ($childItemId != $item) {
712
                            if ($debug) {
713
                                error_log(
714
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
715
                                    0
716
                                );
717
                            }
718
                            // Trying completing parents of failed and browsed items as well.
719
                            if ($childItem->status_is(
720
                                [
721
                                    'completed',
722
                                    'passed',
723
                                    'succeeded',
724
                                    'browsed',
725
                                    'failed',
726
                                ]
727
                            )
728
                            ) {
729
                                // Keep completion status to true.
730
                                continue;
731
                            } else {
732
                                if ($debug > 2) {
733
                                    error_log(
734
                                        '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,
735
                                        0
736
                                    );
737
                                }
738
                                $updateParentStatus = false;
739
                                break;
740
                            }
741
                        }
742
                    }
743
744
                    if ($updateParentStatus) {
745
                        // If all the children were completed:
746
                        $parent->set_status('completed');
747
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
748
                        // Force the status to "completed"
749
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
750
                        $this->update_queue[$parent->get_id()] = 'completed';
751
                        if ($debug) {
752
                            error_log(
753
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
754
                                print_r($this->update_queue, 1),
755
                                0
756
                            );
757
                        }
758
                        // Recursive call.
759
                        $this->autocomplete_parents($parent->get_id());
760
                    }
761
                }
762
            } else {
763
                if ($debug) {
764
                    error_log("Parent #$parent_id does not exists");
765
                }
766
            }
767
        } else {
768
            if ($debug) {
769
                error_log("#$item is an item that doesn't have parents");
770
            }
771
        }
772
    }
773
774
    /**
775
     * Closes the current resource.
776
     *
777
     * Stops the timer
778
     * Saves into the database if required
779
     * Clears the current resource data from this object
780
     *
781
     * @return bool True on success, false on failure
782
     */
783
    public function close()
784
    {
785
        if (empty($this->lp_id)) {
786
            $this->error = 'Trying to close this learnpath but no ID is set';
787
788
            return false;
789
        }
790
        $this->current_time_stop = time();
791
        $this->ordered_items = [];
792
        $this->index = 0;
793
        unset($this->lp_id);
794
        //unset other stuff
795
        return true;
796
    }
797
798
    /**
799
     * Static admin function allowing removal of a learnpath.
800
     *
801
     * @param array  $courseInfo
802
     * @param int    $id         Learnpath ID
803
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
804
     *
805
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
806
     */
807
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
808
    {
809
        $course_id = api_get_course_int_id();
810
        if (!empty($courseInfo)) {
811
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
812
        }
813
814
        // TODO: Implement a way of getting this to work when the current object is not set.
815
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
816
        // If an ID is specifically given and the current LP is not the same, prevent delete.
817
        if (!empty($id) && ($id != $this->lp_id)) {
818
            return false;
819
        }
820
821
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
822
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
823
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
824
825
        // Delete lp item id.
826
        foreach ($this->items as $lpItemId => $dummy) {
827
            $sql = "DELETE FROM $lp_item_view
828
                    WHERE lp_item_id = '".$lpItemId."'";
829
            Database::query($sql);
830
        }
831
832
        // Proposed by Christophe (nickname: clefevre)
833
        $sql = "DELETE FROM $lp_item
834
                WHERE lp_id = ".$this->lp_id;
835
        Database::query($sql);
836
837
        $sql = "DELETE FROM $lp_view
838
                WHERE lp_id = ".$this->lp_id;
839
        Database::query($sql);
840
841
        //self::toggleVisibility($this->lp_id, 0);
842
843
        /*if (2 == $this->type || 3 == $this->type) {
844
            // This is a scorm learning path, delete the files as well.
845
            $sql = "SELECT path FROM $lp
846
                    WHERE iid = ".$this->lp_id;
847
            $res = Database::query($sql);
848
            if (Database::num_rows($res) > 0) {
849
                $row = Database::fetch_array($res);
850
                $path = $row['path'];
851
                $sql = "SELECT iid FROM $lp
852
                        WHERE
853
                            c_id = $course_id AND
854
                            path = '$path' AND
855
                            iid != ".$this->lp_id;
856
                $res = Database::query($sql);
857
                if (Database::num_rows($res) > 0) {
858
                    // Another learning path uses this directory, so don't delete it.
859
                    if ($this->debug > 2) {
860
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
861
                    }
862
                } else {
863
                    // No other LP uses that directory, delete it.
864
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
865
                    // The absolute system path for this course.
866
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
867
                    if ('remove' == $delete && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
868
                        if ($this->debug > 2) {
869
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
870
                        }
871
                        // Proposed by Christophe (clefevre).
872
                        if (0 == strcmp(substr($path, -2), "/.")) {
873
                            $path = substr($path, 0, -1); // Remove "." at the end.
874
                        }
875
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
876
                        rmdirr($course_scorm_dir.$path);
877
                    }
878
                }
879
            }
880
        }*/
881
882
        $table = Database::get_course_table(TABLE_LP_REL_USERGROUP);
883
        $sql = "DELETE FROM $table
884
                WHERE
885
                    lp_id = {$this->lp_id}";
886
        Database::query($sql);
887
888
        $repo = Container::getLpRepository();
889
        $lp = $repo->find($this->lp_id);
890
        Database::getManager()->remove($lp);
891
        Database::getManager()->flush();
892
893
        // Updates the display order of all lps.
894
        $this->update_display_order();
895
896
        $link_info = GradebookUtils::isResourceInCourseGradebook(
897
            api_get_course_id(),
898
            4,
899
            $id,
900
            api_get_session_id()
901
        );
902
903
        if (false !== $link_info) {
904
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
905
        }
906
907
        if ('true' === api_get_setting('search_enabled')) {
908
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
909
        }
910
    }
911
912
    /**
913
     * Removes all the children of one item - dangerous!
914
     *
915
     * @param int $id Element ID of which children have to be removed
916
     *
917
     * @return int Total number of children removed
918
     */
919
    public function delete_children_items($id)
920
    {
921
        $course_id = $this->course_info['real_id'];
922
923
        $num = 0;
924
        $id = (int) $id;
925
        if (empty($id) || empty($course_id)) {
926
            return false;
927
        }
928
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
929
        $sql = "SELECT * FROM $lp_item
930
                WHERE parent_item_id = $id";
931
        $res = Database::query($sql);
932
        while ($row = Database::fetch_array($res)) {
933
            $num += $this->delete_children_items($row['iid']);
934
            $sql = "DELETE FROM $lp_item
935
                    WHERE iid = ".$row['iid'];
936
            Database::query($sql);
937
            $num++;
938
        }
939
940
        return $num;
941
    }
942
943
    /**
944
     * Removes an item from the current learnpath.
945
     *
946
     * @param int $id Elem ID (0 if first)
947
     *
948
     * @return int Number of elements moved
949
     *
950
     * @todo implement resource removal
951
     */
952
    public function delete_item($id)
953
    {
954
        $course_id = api_get_course_int_id();
955
        $id = (int) $id;
956
        // TODO: Implement the resource removal.
957
        if (empty($id) || empty($course_id)) {
958
            return false;
959
        }
960
961
        $repo = Container::getLpItemRepository();
962
        $item = $repo->find($id);
963
        if (null === $item) {
964
            return false;
965
        }
966
967
        $em = Database::getManager();
968
        $repo->removeFromTree($item);
969
        $em->flush();
970
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
971
972
        //Removing prerequisites since the item will not longer exist
973
        $sql_all = "UPDATE $lp_item SET prerequisite = ''
974
                    WHERE prerequisite = '$id'";
975
        Database::query($sql_all);
976
977
        $sql = "UPDATE $lp_item
978
                SET previous_item_id = ".$this->getLastInFirstLevel()."
979
                WHERE lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
980
        Database::query($sql);
981
982
        // Remove from search engine if enabled.
983
        if ('true' === api_get_setting('search_enabled')) {
984
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
985
            $sql = 'SELECT * FROM %s
986
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
987
                    LIMIT 1';
988
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
989
            $res = Database::query($sql);
990
            if (Database::num_rows($res) > 0) {
991
                $row2 = Database::fetch_array($res);
992
                $di = new ChamiloIndexer();
993
                $di->remove_document($row2['search_did']);
994
            }
995
            $sql = 'DELETE FROM %s
996
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
997
                    LIMIT 1';
998
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
999
            Database::query($sql);
1000
        }
1001
    }
1002
1003
    /**
1004
     * Updates an item's content in place.
1005
     *
1006
     * @param int    $id               Element ID
1007
     * @param int    $parent           Parent item ID
1008
     * @param int    $previous         Previous item ID
1009
     * @param string $title            Item title
1010
     * @param string $description      Item description
1011
     * @param string $prerequisites    Prerequisites (optional)
1012
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1013
     * @param int    $max_time_allowed
1014
     * @param string $url
1015
     *
1016
     * @return bool True on success, false on error
1017
     */
1018
    public function edit_item(
1019
        $id,
1020
        $parent,
1021
        $previous,
1022
        $title,
1023
        $description,
1024
        $prerequisites = '0',
1025
        $audio = [],
1026
        $max_time_allowed = 0,
1027
        $url = ''
1028
    ) {
1029
        $_course = api_get_course_info();
1030
        $id = (int) $id;
1031
1032
        if (empty($id) || empty($_course)) {
1033
            return false;
1034
        }
1035
        $repo = Container::getLpItemRepository();
1036
        /** @var CLpItem $item */
1037
        $item = $repo->find($id);
1038
        if (null === $item) {
1039
            return false;
1040
        }
1041
1042
        $item
1043
            ->setTitle($title)
1044
            ->setDescription($description)
1045
            ->setPrerequisite($prerequisites)
1046
            ->setMaxTimeAllowed((int) $max_time_allowed)
1047
        ;
1048
1049
        $em = Database::getManager();
1050
        if (!empty($parent)) {
1051
            $parent = $repo->find($parent);
1052
            $item->setParent($parent);
1053
        } else {
1054
            $item->setParent(null);
1055
        }
1056
1057
        if (!empty($previous)) {
1058
            $previous = $repo->find($previous);
1059
            $repo->persistAsNextSiblingOf( $item, $previous);
1060
            //$repo->reorder($item, 'parent');
1061
            //$repo->persistAsNextSiblingOf()
1062
        } else {
1063
            //$previous = $repo->find($this->get_first_item_id());
1064
            //$repo->persistAsPrevSibling($previous);
1065
            //$repo->persistAsPrevSiblingOf()
1066
            $em->persist($item);
1067
        }
1068
1069
        $em->flush();
1070
        /*$repo->verify();
1071
        $repo->recover();*/
1072
        $audio_update_sql = '';
1073
        // @todo implement audio upload.
1074
        /*if (is_array($audio) && !empty($audio['tmp_name']) && 0 === $audio['error']) {
1075
            // Create the audio folder if it does not exist yet.
1076
            //$filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1077
            if (!is_dir($filepath.'audio')) {
1078
                //mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1079
                $audio_id = DocumentManager::addDocument(
1080
                    $_course,
1081
                    '/audio',
1082
                    'folder',
1083
                    0,
1084
                    'audio'
1085
                );
1086
            }
1087
1088
            // Upload file in documents.
1089
            $pi = pathinfo($audio['name']);
1090
            if ('mp3' === $pi['extension']) {
1091
                $c_det = api_get_course_info($this->cc);
1092
                //$bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1093
                $path = handle_uploaded_document(
1094
                    $c_det,
1095
                    $audio,
1096
                    $bp,
1097
                    '/audio',
1098
                    api_get_user_id(),
1099
                    0,
1100
                    null,
1101
                    0,
1102
                    'rename',
1103
                    false,
1104
                    0
1105
                );
1106
                $path = substr($path, 7);
1107
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1108
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1109
            }
1110
        }*/
1111
1112
        if ('link' === $item->getItemType()) {
1113
            $link = new Link();
1114
            $linkId = $item->getPath();
1115
            $link->updateLink($linkId, $url);
1116
        }
1117
    }
1118
1119
    /**
1120
     * Updates an item's prereq in place.
1121
     *
1122
     * @param int    $id              Element ID
1123
     * @param string $prerequisite_id Prerequisite Element ID
1124
     * @param int    $minScore        Prerequisite min score
1125
     * @param int    $maxScore        Prerequisite max score
1126
     *
1127
     * @return bool True on success, false on error
1128
     */
1129
    public function edit_item_prereq($id, $prerequisite_id, $minScore = 0, $maxScore = 100)
1130
    {
1131
        $id = (int) $id;
1132
1133
        if (empty($id)) {
1134
            return false;
1135
        }
1136
        $prerequisite_id = (int) $prerequisite_id;
1137
1138
        if (empty($minScore) || $minScore < 0) {
1139
            $minScore = 0;
1140
        }
1141
1142
        if (empty($maxScore) || $maxScore < 0) {
1143
            $maxScore = 100;
1144
        }
1145
1146
        $minScore = (float) $minScore;
1147
        $maxScore = (float) $maxScore;
1148
1149
        if (empty($prerequisite_id)) {
1150
            $prerequisite_id = 'NULL';
1151
            $minScore = 0;
1152
            $maxScore = 100;
1153
        }
1154
1155
        $table = Database::get_course_table(TABLE_LP_ITEM);
1156
        $sql = " UPDATE $table
1157
                 SET
1158
                    prerequisite = $prerequisite_id ,
1159
                    prerequisite_min_score = $minScore ,
1160
                    prerequisite_max_score = $maxScore
1161
                 WHERE iid = $id";
1162
        Database::query($sql);
1163
1164
        return true;
1165
    }
1166
1167
    /**
1168
     * Get the specific prefix index terms of this learning path.
1169
     *
1170
     * @param string $prefix
1171
     *
1172
     * @return array Array of terms
1173
     */
1174
    public function get_common_index_terms_by_prefix($prefix)
1175
    {
1176
        $terms = get_specific_field_values_list_by_prefix(
1177
            $prefix,
1178
            $this->cc,
1179
            TOOL_LEARNPATH,
1180
            $this->lp_id
1181
        );
1182
        $prefix_terms = [];
1183
        if (!empty($terms)) {
1184
            foreach ($terms as $term) {
1185
                $prefix_terms[] = $term['value'];
1186
            }
1187
        }
1188
1189
        return $prefix_terms;
1190
    }
1191
1192
    /**
1193
     * Gets the number of items currently completed.
1194
     *
1195
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1196
     *
1197
     * @return int The number of items currently completed
1198
     */
1199
    public function get_complete_items_count($failedStatusException = false)
1200
    {
1201
        $i = 0;
1202
        $completedStatusList = [
1203
            'completed',
1204
            'passed',
1205
            'succeeded',
1206
            'browsed',
1207
        ];
1208
1209
        if (!$failedStatusException) {
1210
            $completedStatusList[] = 'failed';
1211
        }
1212
1213
        foreach ($this->items as $id => $dummy) {
1214
            // Trying failed and browsed considered "progressed" as well.
1215
            if ($this->items[$id]->status_is($completedStatusList) &&
1216
                'dir' !== $this->items[$id]->get_type()
1217
            ) {
1218
                $i++;
1219
            }
1220
        }
1221
1222
        return $i;
1223
    }
1224
1225
    /**
1226
     * Gets the current item ID.
1227
     *
1228
     * @return int The current learnpath item id
1229
     */
1230
    public function get_current_item_id()
1231
    {
1232
        $current = 0;
1233
        if (!empty($this->current)) {
1234
            $current = (int) $this->current;
1235
        }
1236
1237
        return $current;
1238
    }
1239
1240
    /**
1241
     * Force to get the first learnpath item id.
1242
     *
1243
     * @return int The current learnpath item id
1244
     */
1245
    public function get_first_item_id()
1246
    {
1247
        $current = 0;
1248
        if (is_array($this->ordered_items)) {
1249
            $current = $this->ordered_items[0];
1250
        }
1251
1252
        return $current;
1253
    }
1254
1255
    /**
1256
     * Gets the total number of items available for viewing in this SCORM.
1257
     *
1258
     * @return int The total number of items
1259
     */
1260
    public function get_total_items_count()
1261
    {
1262
        return count($this->items);
1263
    }
1264
1265
    /**
1266
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1267
     *
1268
     * @return int The total no-chapters number of items
1269
     */
1270
    public function getTotalItemsCountWithoutDirs()
1271
    {
1272
        $total = 0;
1273
        $typeListNotToCount = self::getChapterTypes();
1274
        foreach ($this->items as $temp2) {
1275
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1276
                $total++;
1277
            }
1278
        }
1279
1280
        return $total;
1281
    }
1282
1283
    /**
1284
     *  Sets the first element URL.
1285
     */
1286
    public function first()
1287
    {
1288
        if ($this->debug > 0) {
1289
            error_log('In learnpath::first()', 0);
1290
            error_log('$this->last_item_seen '.$this->last_item_seen);
1291
        }
1292
1293
        // Test if the last_item_seen exists and is not a dir.
1294
        if (0 == count($this->ordered_items)) {
1295
            $this->index = 0;
1296
        }
1297
1298
        if (!empty($this->last_item_seen) &&
1299
            !empty($this->items[$this->last_item_seen]) &&
1300
            'dir' != $this->items[$this->last_item_seen]->get_type()
1301
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1302
            //&& !$this->items[$this->last_item_seen]->is_done()
1303
        ) {
1304
            if ($this->debug > 2) {
1305
                error_log(
1306
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1307
                    $this->items[$this->last_item_seen]->get_type()
1308
                );
1309
            }
1310
            $index = -1;
1311
            foreach ($this->ordered_items as $myindex => $item_id) {
1312
                if ($item_id == $this->last_item_seen) {
1313
                    $index = $myindex;
1314
                    break;
1315
                }
1316
            }
1317
            if (-1 == $index) {
1318
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1319
                if ($this->debug > 2) {
1320
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1321
                }
1322
1323
                return false;
1324
            } else {
1325
                $this->last = $this->last_item_seen;
1326
                $this->current = $this->last_item_seen;
1327
                $this->index = $index;
1328
            }
1329
        } else {
1330
            if ($this->debug > 2) {
1331
                error_log('In learnpath::first() - No last item seen', 0);
1332
            }
1333
            $index = 0;
1334
            // Loop through all ordered items and stop at the first item that is
1335
            // not a directory *and* that has not been completed yet.
1336
            while (!empty($this->ordered_items[$index]) &&
1337
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1338
                (
1339
                    'dir' === $this->items[$this->ordered_items[$index]]->get_type() ||
1340
                    true === $this->items[$this->ordered_items[$index]]->is_done()
1341
                ) && $index < $this->max_ordered_items) {
1342
                $index++;
1343
            }
1344
1345
            $this->last = $this->current;
1346
            // current is
1347
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1348
            $this->index = $index;
1349
            if ($this->debug > 2) {
1350
                error_log('$index '.$index);
1351
                error_log('In learnpath::first() - No last item seen');
1352
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1353
            }
1354
        }
1355
        if ($this->debug > 2) {
1356
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1357
        }
1358
    }
1359
1360
    /**
1361
     * Gets the js library from the database.
1362
     *
1363
     * @return string The name of the javascript library to be used
1364
     */
1365
    public function get_js_lib()
1366
    {
1367
        $lib = '';
1368
        if (!empty($this->js_lib)) {
1369
            $lib = $this->js_lib;
1370
        }
1371
1372
        return $lib;
1373
    }
1374
1375
    /**
1376
     * Gets the learnpath database ID.
1377
     *
1378
     * @return int Learnpath ID in the lp table
1379
     */
1380
    public function get_id()
1381
    {
1382
        if (!empty($this->lp_id)) {
1383
            return (int) $this->lp_id;
1384
        }
1385
1386
        return 0;
1387
    }
1388
1389
    /**
1390
     * Gets the last element URL.
1391
     *
1392
     * @return string URL to load into the viewer
1393
     */
1394
    public function get_last()
1395
    {
1396
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1397
        if (count($this->ordered_items) > 0) {
1398
            $this->index = count($this->ordered_items) - 1;
1399
1400
            return $this->ordered_items[$this->index];
1401
        }
1402
1403
        return false;
1404
    }
1405
1406
    /**
1407
     * Get the last element in the first level.
1408
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1409
     *
1410
     * @return mixed
1411
     */
1412
    public function getLastInFirstLevel()
1413
    {
1414
        try {
1415
            $lastId = Database::getManager()
1416
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1417
                WHERE i.lp = :lp AND i.parent IS NULL AND i.itemType != :type ORDER BY i.displayOrder DESC')
1418
                ->setMaxResults(1)
1419
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1420
                ->getSingleScalarResult();
1421
1422
            return $lastId;
1423
        } catch (Exception $exception) {
1424
            return 0;
1425
        }
1426
    }
1427
1428
    /**
1429
     * Gets the navigation bar for the learnpath display screen.
1430
     *
1431
     * @param string $barId
1432
     *
1433
     * @return string The HTML string to use as a navigation bar
1434
     */
1435
    public function get_navigation_bar($barId = '')
1436
    {
1437
        if (empty($barId)) {
1438
            $barId = 'control-top';
1439
        }
1440
        $lpId = $this->lp_id;
1441
        $mycurrentitemid = $this->get_current_item_id();
1442
        $reportingText = get_lang('Reporting');
1443
        $previousText = get_lang('Previous');
1444
        $nextText = get_lang('Next');
1445
        $fullScreenText = get_lang('Back to normal screen');
1446
1447
        $settings = api_get_configuration_value('lp_view_settings');
1448
        $display = $settings['display'] ?? false;
1449
        $reportingIcon = '
1450
            <a class="icon-toolbar"
1451
                id="stats_link"
1452
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
1453
                onclick="window.parent.API.save_asset(); return true;"
1454
                target="content_name" title="'.$reportingText.'">
1455
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1456
            </a>';
1457
1458
        if (!empty($display)) {
1459
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1460
            if (false === $showReporting) {
1461
                $reportingIcon = '';
1462
            }
1463
        }
1464
1465
        $hideArrows = false;
1466
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1467
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1468
        }
1469
1470
        $previousIcon = '';
1471
        $nextIcon = '';
1472
        if (false === $hideArrows) {
1473
            $previousIcon = '
1474
                <a class="icon-toolbar" id="scorm-previous" href="#"
1475
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1476
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1477
                </a>';
1478
1479
            $nextIcon = '
1480
                <a class="icon-toolbar" id="scorm-next" href="#"
1481
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1482
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1483
                </a>';
1484
        }
1485
1486
        if ('fullscreen' === $this->mode) {
1487
            $navbar = '
1488
                  <span id="'.$barId.'" class="buttons">
1489
                    '.$reportingIcon.'
1490
                    '.$previousIcon.'
1491
                    '.$nextIcon.'
1492
                    <a class="icon-toolbar" id="view-embedded"
1493
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1494
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1495
                    </a>
1496
                  </span>';
1497
        } else {
1498
            $navbar = '
1499
                 <span id="'.$barId.'" class="buttons text-right">
1500
                    '.$reportingIcon.'
1501
                    '.$previousIcon.'
1502
                    '.$nextIcon.'
1503
                </span>';
1504
        }
1505
1506
        return $navbar;
1507
    }
1508
1509
    /**
1510
     * Gets the next resource in queue (url).
1511
     *
1512
     * @return string URL to load into the viewer
1513
     */
1514
    public function get_next_index()
1515
    {
1516
        // TODO
1517
        $index = $this->index;
1518
        $index++;
1519
        while (
1520
            !empty($this->ordered_items[$index]) && ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) &&
1521
            $index < $this->max_ordered_items
1522
        ) {
1523
            $index++;
1524
            if ($index == $this->max_ordered_items) {
1525
                if ('dir' === $this->items[$this->ordered_items[$index]]->get_type()) {
1526
                    return $this->index;
1527
                }
1528
1529
                return $index;
1530
            }
1531
        }
1532
        if (empty($this->ordered_items[$index])) {
1533
            return $this->index;
1534
        }
1535
1536
        return $index;
1537
    }
1538
1539
    /**
1540
     * Gets item_id for the next element.
1541
     *
1542
     * @return int Next item (DB) ID
1543
     */
1544
    public function get_next_item_id()
1545
    {
1546
        $new_index = $this->get_next_index();
1547
        if (!empty($new_index)) {
1548
            if (isset($this->ordered_items[$new_index])) {
1549
                return $this->ordered_items[$new_index];
1550
            }
1551
        }
1552
1553
        return 0;
1554
    }
1555
1556
    /**
1557
     * Returns the package type ('scorm','aicc','scorm2004','ppt'...).
1558
     *
1559
     * Generally, the package provided is in the form of a zip file, so the function
1560
     * has been written to test a zip file. If not a zip, the function will return the
1561
     * default return value: ''
1562
     *
1563
     * @param string $filePath the path to the file
1564
     * @param string $file_name the original name of the file
1565
     *
1566
     * @return string 'scorm','aicc','scorm2004','error-empty-package'
1567
     *                if the package is empty, or '' if the package cannot be recognized
1568
     */
1569
    public static function getPackageType($filePath, $file_name)
1570
    {
1571
        // Get name of the zip file without the extension.
1572
        $file_info = pathinfo($file_name);
1573
        $extension = $file_info['extension']; // Extension only.
1574
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
1575
                'dll',
1576
                'exe',
1577
            ])) {
1578
            return 'oogie';
1579
        }
1580
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
1581
                'dll',
1582
                'exe',
1583
            ])) {
1584
            return 'woogie';
1585
        }
1586
1587
        $zipFile = new ZipFile();
1588
        $zipFile->openFile($filePath);
1589
        $zipContentArray = $zipFile->getEntries();
1590
        $package_type = '';
1591
        $manifest = '';
1592
        $aicc_match_crs = 0;
1593
        $aicc_match_au = 0;
1594
        $aicc_match_des = 0;
1595
        $aicc_match_cst = 0;
1596
        $countItems = 0;
1597
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
1598
        if ($zipContentArray) {
1599
            $countItems = count($zipContentArray);
1600
            if ($countItems > 0) {
1601
                foreach ($zipContentArray as $thisContent) {
1602
                    $fileName = basename($thisContent->getName());
1603
                    if (preg_match('~.(php.*|phtml)$~i', $fileName)) {
1604
                        // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
1605
                    } elseif (false !== stristr($fileName, 'imsmanifest.xml')) {
1606
                        $manifest = $fileName; // Just the relative directory inside scorm/
1607
                        $package_type = 'scorm';
1608
                        break; // Exit the foreach loop.
1609
                    } elseif (
1610
                        preg_match('/aicc\//i', $fileName) ||
1611
                        in_array(
1612
                            strtolower(pathinfo($fileName, PATHINFO_EXTENSION)),
1613
                            ['crs', 'au', 'des', 'cst']
1614
                        )
1615
                    ) {
1616
                        $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
1617
                        switch ($ext) {
1618
                            case 'crs':
1619
                                $aicc_match_crs = 1;
1620
                                break;
1621
                            case 'au':
1622
                                $aicc_match_au = 1;
1623
                                break;
1624
                            case 'des':
1625
                                $aicc_match_des = 1;
1626
                                break;
1627
                            case 'cst':
1628
                                $aicc_match_cst = 1;
1629
                                break;
1630
                            default:
1631
                                break;
1632
                        }
1633
                        //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
1634
                    } else {
1635
                        $package_type = '';
1636
                    }
1637
                }
1638
            }
1639
        }
1640
1641
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
1642
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
1643
            $package_type = 'aicc';
1644
        }
1645
1646
        // Try with chamilo course builder
1647
        if (empty($package_type)) {
1648
            // Sometimes users will try to upload an empty zip, or a zip with
1649
            // only a folder. Catch that and make the calling function aware.
1650
            // If the single file was the imsmanifest.xml, then $package_type
1651
            // would be 'scorm' and we wouldn't be here.
1652
            if ($countItems < 2) {
1653
                return 'error-empty-package';
1654
            }
1655
            $package_type = 'chamilo';
1656
        }
1657
1658
        return $package_type;
1659
    }
1660
1661
    /**
1662
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
1663
     *
1664
     * @return string URL to load into the viewer
1665
     */
1666
    public function get_previous_index()
1667
    {
1668
        $index = $this->index;
1669
        if (isset($this->ordered_items[$index - 1])) {
1670
            $index--;
1671
            while (isset($this->ordered_items[$index]) &&
1672
                ('dir' === $this->items[$this->ordered_items[$index]]->get_type())
1673
            ) {
1674
                $index--;
1675
                if ($index < 0) {
1676
                    return $this->index;
1677
                }
1678
            }
1679
        }
1680
1681
        return $index;
1682
    }
1683
1684
    /**
1685
     * Gets item_id for the next element.
1686
     *
1687
     * @return int Previous item (DB) ID
1688
     */
1689
    public function get_previous_item_id()
1690
    {
1691
        $index = $this->get_previous_index();
1692
1693
        return $this->ordered_items[$index];
1694
    }
1695
1696
    /**
1697
     * Returns the HTML necessary to print a mediaplayer block inside a page.
1698
     *
1699
     * @param int    $lpItemId
1700
     * @param string $autostart
1701
     *
1702
     * @return string The mediaplayer HTML
1703
     */
1704
    public function get_mediaplayer($lpItemId, $autostart = 'true')
1705
    {
1706
        $courseInfo = api_get_course_info();
1707
        $lpItemId = (int) $lpItemId;
1708
1709
        if (empty($courseInfo) || empty($lpItemId)) {
1710
            return '';
1711
        }
1712
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
1713
1714
        if (empty($item)) {
1715
            return '';
1716
        }
1717
1718
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1719
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1720
        $itemViewId = (int) $item->db_item_view_id;
1721
1722
        // Getting all the information about the item.
1723
        $sql = "SELECT lp_view.status
1724
                FROM $tbl_lp_item as lpi
1725
                INNER JOIN $tbl_lp_item_view as lp_view
1726
                ON (lpi.iid = lp_view.lp_item_id)
1727
                WHERE
1728
                    lp_view.iid = $itemViewId AND
1729
                    lpi.iid = $lpItemId
1730
                ";
1731
        $result = Database::query($sql);
1732
        $row = Database::fetch_assoc($result);
1733
        $output = '';
1734
        $audio = $item->audio;
1735
1736
        if (!empty($audio)) {
1737
            $list = $_SESSION['oLP']->get_toc();
1738
1739
            switch ($item->get_type()) {
1740
                case 'quiz':
1741
                    $type_quiz = false;
1742
                    foreach ($list as $toc) {
1743
                        if ($toc['id'] == $_SESSION['oLP']->current) {
1744
                            $type_quiz = true;
1745
                        }
1746
                    }
1747
1748
                    if ($type_quiz) {
1749
                        if (1 == $_SESSION['oLP']->prevent_reinit) {
1750
                            $autostart_audio = 'completed' === $row['status'] ? 'false' : 'true';
1751
                        } else {
1752
                            $autostart_audio = $autostart;
1753
                        }
1754
                    }
1755
                    break;
1756
                case TOOL_READOUT_TEXT:
1757
                    $autostart_audio = 'false';
1758
                    break;
1759
                default:
1760
                    $autostart_audio = 'true';
1761
            }
1762
1763
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'.$audio;
1764
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document'.$audio.'?'.api_get_cidreq();
1765
1766
            $player = Display::getMediaPlayer(
1767
                $file,
1768
                [
1769
                    'id' => 'lp_audio_media_player',
1770
                    'url' => $url,
1771
                    'autoplay' => $autostart_audio,
1772
                    'width' => '100%',
1773
                ]
1774
            );
1775
1776
            // The mp3 player.
1777
            $output = '<div id="container">';
1778
            $output .= $player;
1779
            $output .= '</div>';
1780
        }
1781
1782
        return $output;
1783
    }
1784
1785
    /**
1786
     * @param int    $studentId
1787
     * @param int    $prerequisite
1788
     * @param Course $course
1789
     * @param int    $sessionId
1790
     *
1791
     * @return bool
1792
     */
1793
    public static function isBlockedByPrerequisite(
1794
        $studentId,
1795
        $prerequisite,
1796
        Course $course,
1797
        $sessionId
1798
    ) {
1799
        if (null === $course) {
1800
            return false;
1801
        }
1802
1803
        $courseId = $course->getId();
1804
1805
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
1806
        if ($allow) {
1807
            if (api_is_allowed_to_edit() ||
1808
                api_is_platform_admin(true) ||
1809
                api_is_drh() ||
1810
                api_is_coach($sessionId, $courseId, false)
1811
            ) {
1812
                return false;
1813
            }
1814
        }
1815
1816
        $isBlocked = false;
1817
        if (!empty($prerequisite)) {
1818
            $progress = self::getProgress(
1819
                $prerequisite,
1820
                $studentId,
1821
                $courseId,
1822
                $sessionId
1823
            );
1824
            if ($progress < 100) {
1825
                $isBlocked = true;
1826
            }
1827
1828
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
1829
                // Block if it does not exceed minimum time
1830
                // Minimum time (in minutes) to pass the learning path
1831
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
1832
1833
                if ($accumulateWorkTime > 0) {
1834
                    // Total time in course (sum of times in learning paths from course)
1835
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
1836
1837
                    // Connect with the plugin_licences_course_session table
1838
                    // which indicates what percentage of the time applies
1839
                    // Minimum connection percentage
1840
                    $perc = 100;
1841
                    // Time from the course
1842
                    $tc = $accumulateWorkTimeTotal;
1843
1844
                    // Percentage of the learning paths
1845
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
1846
                    // Minimum time for each learning path
1847
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
1848
1849
                    // Spent time (in seconds) so far in the learning path
1850
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
1851
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
1852
1853
                    if ($lpTime < ($accumulateWorkTime * 60)) {
1854
                        $isBlocked = true;
1855
                    }
1856
                }
1857
            }
1858
        }
1859
1860
        return $isBlocked;
1861
    }
1862
1863
    /**
1864
     * Checks if the learning path is visible for student after the progress
1865
     * of its prerequisite is completed, considering the time availability and
1866
     * the LP visibility.
1867
     */
1868
    public static function is_lp_visible_for_student(CLp $lp, $student_id, Course $course, SessionEntity $session = null): bool
1869
    {
1870
        if (null === $course) {
1871
            return false;
1872
        }
1873
1874
        $sessionId = $session ? $session->getId() : 0;
1875
        $courseId = $course->getId();
1876
        $visibility = $lp->isVisible($course, $session);
1877
1878
        // If the item was deleted.
1879
        if (false === $visibility) {
1880
            return false;
1881
        }
1882
1883
        $now = time();
1884
        if ($lp->hasCategory()) {
1885
            $category = $lp->getCategory();
1886
1887
            if (false === self::categoryIsVisibleForStudent(
1888
                    $category,
1889
                    api_get_user_entity($student_id),
1890
                    $course,
1891
                    $session
1892
                )) {
1893
                return false;
1894
            }
1895
1896
            $prerequisite = $lp->getPrerequisite();
1897
            $is_visible = true;
1898
1899
            $isBlocked = self::isBlockedByPrerequisite(
1900
                $student_id,
1901
                $prerequisite,
1902
                $course,
1903
                $sessionId
1904
            );
1905
1906
            if ($isBlocked) {
1907
                $is_visible = false;
1908
            }
1909
1910
            // Also check the time availability of the LP
1911
            if ($is_visible) {
1912
                // Adding visibility restrictions
1913
                if (null !== $lp->getPublicatedOn()) {
1914
                    if ($now < $lp->getPublicatedOn()->getTimestamp()) {
1915
                        $is_visible = false;
1916
                    }
1917
                }
1918
                // Blocking empty start times see BT#2800
1919
                global $_custom;
1920
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
1921
                    $_custom['lps_hidden_when_no_start_date']
1922
                ) {
1923
                    if (null !== $lp->getPublicatedOn()) {
1924
                        $is_visible = false;
1925
                    }
1926
                }
1927
1928
                if (null !== $lp->getExpiredOn()) {
1929
                    if ($now > $lp->getExpiredOn()->getTimestamp()) {
1930
                        $is_visible = false;
1931
                    }
1932
                }
1933
            }
1934
1935
            if ($is_visible) {
1936
                $subscriptionSettings = self::getSubscriptionSettings();
1937
1938
                // Check if the subscription users/group to a LP is ON
1939
                if (1 == $lp->getSubscribeUsers() &&
1940
                    true === $subscriptionSettings['allow_add_users_to_lp']
1941
                ) {
1942
                    // Try group
1943
                    $is_visible = false;
1944
                    // Checking only the user visibility
1945
                    // @todo fix visibility
1946
                    $userVisibility = 1;
1947
                    if (1 == $userVisibility) {
1948
                        $is_visible = true;
1949
                    } else {
1950
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
1951
                        if (!empty($userGroups)) {
1952
                            foreach ($userGroups as $groupInfo) {
1953
                                $groupId = $groupInfo['iid'];
1954
                                // @todo fix visibility.
1955
                                $userVisibility = 1;
1956
                                if (1 == $userVisibility) {
1957
                                    $is_visible = true;
1958
                                    break;
1959
                                }
1960
                            }
1961
                        }
1962
                    }
1963
                }
1964
            }
1965
1966
            return $is_visible;
1967
        }
1968
1969
        return true;
1970
    }
1971
1972
    /**
1973
     * @param int $lpId
1974
     * @param int $userId
1975
     * @param int $courseId
1976
     * @param int $sessionId
1977
     *
1978
     * @return int
1979
     */
1980
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
1981
    {
1982
        $lpId = (int) $lpId;
1983
        $userId = (int) $userId;
1984
        $courseId = (int) $courseId;
1985
        $sessionId = (int) $sessionId;
1986
1987
        $sessionCondition = api_get_session_condition($sessionId);
1988
        $table = Database::get_course_table(TABLE_LP_VIEW);
1989
        $sql = "SELECT progress FROM $table
1990
                WHERE
1991
                    c_id = $courseId AND
1992
                    lp_id = $lpId AND
1993
                    user_id = $userId $sessionCondition ";
1994
        $res = Database::query($sql);
1995
1996
        $progress = 0;
1997
        if (Database::num_rows($res) > 0) {
1998
            $row = Database::fetch_array($res);
1999
            $progress = (int) $row['progress'];
2000
        }
2001
2002
        return $progress;
2003
    }
2004
2005
    /**
2006
     * @param array $lpList
2007
     * @param int   $userId
2008
     * @param int   $courseId
2009
     * @param int   $sessionId
2010
     *
2011
     * @return array
2012
     */
2013
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2014
    {
2015
        $lpList = array_map('intval', $lpList);
2016
        if (empty($lpList)) {
2017
            return [];
2018
        }
2019
2020
        $lpList = implode("','", $lpList);
2021
2022
        $userId = (int) $userId;
2023
        $courseId = (int) $courseId;
2024
        $sessionId = (int) $sessionId;
2025
2026
        $sessionCondition = api_get_session_condition($sessionId);
2027
        $table = Database::get_course_table(TABLE_LP_VIEW);
2028
        $sql = "SELECT lp_id, progress FROM $table
2029
                WHERE
2030
                    c_id = $courseId AND
2031
                    lp_id IN ('".$lpList."') AND
2032
                    user_id = $userId $sessionCondition ";
2033
        $res = Database::query($sql);
2034
2035
        if (Database::num_rows($res) > 0) {
2036
            $list = [];
2037
            while ($row = Database::fetch_array($res)) {
2038
                $list[$row['lp_id']] = $row['progress'];
2039
            }
2040
2041
            return $list;
2042
        }
2043
2044
        return [];
2045
    }
2046
2047
    /**
2048
     * Displays a progress bar
2049
     * completed so far.
2050
     *
2051
     * @param int    $percentage Progress value to display
2052
     * @param string $text_add   Text to display near the progress value
2053
     *
2054
     * @return string HTML string containing the progress bar
2055
     */
2056
    public static function get_progress_bar($percentage = -1, $text_add = '')
2057
    {
2058
        $text = $percentage.$text_add;
2059
2060
        return '<div class="progress">
2061
            <div id="progress_bar_value"
2062
                class="progress-bar progress-bar-success" role="progressbar"
2063
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2064
            '.$text.'
2065
            </div>
2066
        </div>';
2067
    }
2068
2069
    /**
2070
     * @param string $mode can be '%' or 'abs'
2071
     *                     otherwise this value will be used $this->progress_bar_mode
2072
     *
2073
     * @return string
2074
     */
2075
    public function getProgressBar($mode = null)
2076
    {
2077
        [$percentage, $text_add] = $this->get_progress_bar_text($mode);
2078
2079
        return self::get_progress_bar($percentage, $text_add);
2080
    }
2081
2082
    /**
2083
     * Gets the progress bar info to display inside the progress bar.
2084
     * Also used by scorm_api.php.
2085
     *
2086
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2087
     *                     we display a number of completed elements per total elements
2088
     * @param int    $add  Additional steps to fake as completed
2089
     *
2090
     * @return array Percentage or number and symbol (% or /xx)
2091
     */
2092
    public function get_progress_bar_text($mode = '', $add = 0)
2093
    {
2094
        if (empty($mode)) {
2095
            $mode = $this->progress_bar_mode;
2096
        }
2097
        $text = '';
2098
        $percentage = 0;
2099
        // If the option to use the score as progress is set for this learning
2100
        // path, then the rules are completely different: we assume only one
2101
        // item exists and the progress of the LP depends on the score
2102
        $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
2103
        if (true === $scoreAsProgressSetting) {
2104
            $scoreAsProgress = $this->getUseScoreAsProgress();
2105
            if ($scoreAsProgress) {
2106
                // Get single item's score
2107
                $itemId = $this->get_current_item_id();
2108
                $item = $this->getItem($itemId);
2109
                $score = $item->get_score();
2110
                $maxScore = $item->get_max();
2111
                if ($mode = '%') {
2112
                    if (!empty($maxScore)) {
2113
                        $percentage = ((float) $score / (float) $maxScore) * 100;
2114
                    }
2115
                    $percentage = number_format($percentage, 0);
2116
                    $text = '%';
2117
                } else {
2118
                    $percentage = $score;
2119
                    $text = '/'.$maxScore;
2120
                }
2121
2122
                return [$percentage, $text];
2123
            }
2124
        }
2125
        // otherwise just continue the normal processing of progress
2126
        $total_items = $this->getTotalItemsCountWithoutDirs();
2127
        $completeItems = $this->get_complete_items_count();
2128
        if (0 != $add) {
2129
            $completeItems += $add;
2130
        }
2131
        if ($completeItems > $total_items) {
2132
            $completeItems = $total_items;
2133
        }
2134
        if ('%' === $mode) {
2135
            if ($total_items > 0) {
2136
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2137
            }
2138
            $percentage = number_format($percentage, 0);
2139
            $text = '%';
2140
        } elseif ('abs' === $mode) {
2141
            $percentage = $completeItems;
2142
            $text = '/'.$total_items;
2143
        }
2144
2145
        return [
2146
            $percentage,
2147
            $text,
2148
        ];
2149
    }
2150
2151
    /**
2152
     * Gets the progress bar mode.
2153
     *
2154
     * @return string The progress bar mode attribute
2155
     */
2156
    public function get_progress_bar_mode()
2157
    {
2158
        if (!empty($this->progress_bar_mode)) {
2159
            return $this->progress_bar_mode;
2160
        }
2161
2162
        return '%';
2163
    }
2164
2165
    /**
2166
     * Gets the learnpath theme (remote or local).
2167
     *
2168
     * @return string Learnpath theme
2169
     */
2170
    public function get_theme()
2171
    {
2172
        if (!empty($this->theme)) {
2173
            return $this->theme;
2174
        }
2175
2176
        return '';
2177
    }
2178
2179
    /**
2180
     * Gets the learnpath session id.
2181
     *
2182
     * @return int
2183
     */
2184
    public function get_lp_session_id()
2185
    {
2186
        if (!empty($this->lp_session_id)) {
2187
            return (int) $this->lp_session_id;
2188
        }
2189
2190
        return 0;
2191
    }
2192
2193
    /**
2194
     * Generate a new prerequisites string for a given item. If this item was a sco and
2195
     * its prerequisites were strings (instead of IDs), then transform those strings into
2196
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2197
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2198
     * same rule as the scormExport() method.
2199
     *
2200
     * @param int $item_id Item ID
2201
     *
2202
     * @return string Prerequisites string ready for the export as SCORM
2203
     */
2204
    public function get_scorm_prereq_string($item_id)
2205
    {
2206
        if ($this->debug > 0) {
2207
            error_log('In learnpath::get_scorm_prereq_string()');
2208
        }
2209
        if (!is_object($this->items[$item_id])) {
2210
            return false;
2211
        }
2212
        /** @var learnpathItem $oItem */
2213
        $oItem = $this->items[$item_id];
2214
        $prereq = $oItem->get_prereq_string();
2215
2216
        if (empty($prereq)) {
2217
            return '';
2218
        }
2219
        if (preg_match('/^\d+$/', $prereq) &&
2220
            isset($this->items[$prereq]) &&
2221
            is_object($this->items[$prereq])
2222
        ) {
2223
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2224
            // then simply return it (with the ITEM_ prefix).
2225
            //return 'ITEM_' . $prereq;
2226
            return $this->items[$prereq]->ref;
2227
        } else {
2228
            if (isset($this->refs_list[$prereq])) {
2229
                // It's a simple string item from which the ID can be found in the refs list,
2230
                // so we can transform it directly to an ID for export.
2231
                return $this->items[$this->refs_list[$prereq]]->ref;
2232
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2233
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2234
            } else {
2235
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2236
                // and replace them, one by one, by the internal IDs (chamilo db)
2237
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2238
                // by a space as well.
2239
                $find = [
2240
                    '&',
2241
                    '|',
2242
                    '~',
2243
                    '=',
2244
                    '<>',
2245
                    '{',
2246
                    '}',
2247
                    '*',
2248
                    '(',
2249
                    ')',
2250
                ];
2251
                $replace = [
2252
                    ' ',
2253
                    ' ',
2254
                    ' ',
2255
                    ' ',
2256
                    ' ',
2257
                    ' ',
2258
                    ' ',
2259
                    ' ',
2260
                    ' ',
2261
                    ' ',
2262
                ];
2263
                $prereq_mod = str_replace($find, $replace, $prereq);
2264
                $ids = explode(' ', $prereq_mod);
2265
                foreach ($ids as $id) {
2266
                    $id = trim($id);
2267
                    if (isset($this->refs_list[$id])) {
2268
                        $prereq = preg_replace(
2269
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2270
                            'ITEM_'.$this->refs_list[$id],
2271
                            $prereq
2272
                        );
2273
                    }
2274
                }
2275
2276
                return $prereq;
2277
            }
2278
        }
2279
    }
2280
2281
    /**
2282
     * Returns the XML DOM document's node.
2283
     *
2284
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2285
     * @param string   $id       The identifier to look for
2286
     *
2287
     * @return mixed The reference to the element found with that identifier. False if not found
2288
     */
2289
    public function get_scorm_xml_node(&$children, $id)
2290
    {
2291
        for ($i = 0; $i < $children->length; $i++) {
2292
            $item_temp = $children->item($i);
2293
            if ('item' === $item_temp->nodeName) {
2294
                if ($item_temp->getAttribute('identifier') == $id) {
2295
                    return $item_temp;
2296
                }
2297
            }
2298
            $subchildren = $item_temp->childNodes;
2299
            if ($subchildren && $subchildren->length > 0) {
2300
                $val = $this->get_scorm_xml_node($subchildren, $id);
2301
                if (is_object($val)) {
2302
                    return $val;
2303
                }
2304
            }
2305
        }
2306
2307
        return false;
2308
    }
2309
2310
    /**
2311
     * Gets the status list for all LP's items.
2312
     *
2313
     * @return array Array of [index] => [item ID => current status]
2314
     */
2315
    public function get_items_status_list()
2316
    {
2317
        $list = [];
2318
        foreach ($this->ordered_items as $item_id) {
2319
            $list[] = [
2320
                $item_id => $this->items[$item_id]->get_status(),
2321
            ];
2322
        }
2323
2324
        return $list;
2325
    }
2326
2327
    /**
2328
     * Return the number of interactions for the given learnpath Item View ID.
2329
     * This method can be used as static.
2330
     *
2331
     * @param int $lp_iv_id  Item View ID
2332
     * @param int $course_id course id
2333
     *
2334
     * @return int
2335
     */
2336
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2337
    {
2338
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2339
        $lp_iv_id = (int) $lp_iv_id;
2340
        $course_id = (int) $course_id;
2341
2342
        $sql = "SELECT count(*) FROM $table
2343
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2344
        $res = Database::query($sql);
2345
        $num = 0;
2346
        if (Database::num_rows($res)) {
2347
            $row = Database::fetch_array($res);
2348
            $num = $row[0];
2349
        }
2350
2351
        return $num;
2352
    }
2353
2354
    /**
2355
     * Return the interactions as an array for the given lp_iv_id.
2356
     * This method can be used as static.
2357
     *
2358
     * @param int $lp_iv_id Learnpath Item View ID
2359
     *
2360
     * @return array
2361
     *
2362
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2363
     */
2364
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2365
    {
2366
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2367
        $list = [];
2368
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2369
        $lp_iv_id = (int) $lp_iv_id;
2370
2371
        if (empty($lp_iv_id) || empty($course_id)) {
2372
            return [];
2373
        }
2374
2375
        $sql = "SELECT * FROM $table
2376
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2377
                ORDER BY order_id ASC";
2378
        $res = Database::query($sql);
2379
        $num = Database::num_rows($res);
2380
        if ($num > 0) {
2381
            $list[] = [
2382
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2383
                'id' => api_htmlentities(get_lang('Interaction ID'), ENT_QUOTES),
2384
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2385
                'time' => api_htmlentities(get_lang('Time (finished at...)'), ENT_QUOTES),
2386
                'correct_responses' => api_htmlentities(get_lang('Correct answers'), ENT_QUOTES),
2387
                'student_response' => api_htmlentities(get_lang('Learner answers'), ENT_QUOTES),
2388
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2389
                'latency' => api_htmlentities(get_lang('Time spent'), ENT_QUOTES),
2390
                'student_response_formatted' => '',
2391
            ];
2392
            while ($row = Database::fetch_array($res)) {
2393
                $studentResponseFormatted = urldecode($row['student_response']);
2394
                $content_student_response = explode('__|', $studentResponseFormatted);
2395
                if (count($content_student_response) > 0) {
2396
                    if (count($content_student_response) >= 3) {
2397
                        // Pop the element off the end of array.
2398
                        array_pop($content_student_response);
2399
                    }
2400
                    $studentResponseFormatted = implode(',', $content_student_response);
2401
                }
2402
2403
                $list[] = [
2404
                    'order_id' => $row['order_id'] + 1,
2405
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2406
                    'type' => $row['interaction_type'],
2407
                    'time' => $row['completion_time'],
2408
                    'correct_responses' => '', // Hide correct responses from students.
2409
                    'student_response' => $row['student_response'],
2410
                    'result' => $row['result'],
2411
                    'latency' => $row['latency'],
2412
                    'student_response_formatted' => $studentResponseFormatted,
2413
                ];
2414
            }
2415
        }
2416
2417
        return $list;
2418
    }
2419
2420
    /**
2421
     * Return the number of objectives for the given learnpath Item View ID.
2422
     * This method can be used as static.
2423
     *
2424
     * @param int $lp_iv_id  Item View ID
2425
     * @param int $course_id Course ID
2426
     *
2427
     * @return int Number of objectives
2428
     */
2429
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2430
    {
2431
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2432
        $course_id = (int) $course_id;
2433
        $lp_iv_id = (int) $lp_iv_id;
2434
        $sql = "SELECT count(*) FROM $table
2435
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2436
        //@todo seems that this always returns 0
2437
        $res = Database::query($sql);
2438
        $num = 0;
2439
        if (Database::num_rows($res)) {
2440
            $row = Database::fetch_array($res);
2441
            $num = $row[0];
2442
        }
2443
2444
        return $num;
2445
    }
2446
2447
    /**
2448
     * Return the objectives as an array for the given lp_iv_id.
2449
     * This method can be used as static.
2450
     *
2451
     * @param int $lpItemViewId Learnpath Item View ID
2452
     * @param int $course_id
2453
     *
2454
     * @return array
2455
     *
2456
     * @todo    Translate labels
2457
     */
2458
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
2459
    {
2460
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2461
        $lpItemViewId = (int) $lpItemViewId;
2462
2463
        if (empty($course_id) || empty($lpItemViewId)) {
2464
            return [];
2465
        }
2466
2467
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2468
        $sql = "SELECT * FROM $table
2469
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
2470
                ORDER BY order_id ASC";
2471
        $res = Database::query($sql);
2472
        $num = Database::num_rows($res);
2473
        $list = [];
2474
        if ($num > 0) {
2475
            $list[] = [
2476
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2477
                'objective_id' => api_htmlentities(get_lang('Objective ID'), ENT_QUOTES),
2478
                'score_raw' => api_htmlentities(get_lang('Objective raw score'), ENT_QUOTES),
2479
                'score_max' => api_htmlentities(get_lang('Objective max score'), ENT_QUOTES),
2480
                'score_min' => api_htmlentities(get_lang('Objective min score'), ENT_QUOTES),
2481
                'status' => api_htmlentities(get_lang('Objective status'), ENT_QUOTES),
2482
            ];
2483
            while ($row = Database::fetch_array($res)) {
2484
                $list[] = [
2485
                    'order_id' => $row['order_id'] + 1,
2486
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
2487
                    'score_raw' => $row['score_raw'],
2488
                    'score_max' => $row['score_max'],
2489
                    'score_min' => $row['score_min'],
2490
                    'status' => $row['status'],
2491
                ];
2492
            }
2493
        }
2494
2495
        return $list;
2496
    }
2497
2498
    /**
2499
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
2500
     * used by get_html_toc() to be ready to display.
2501
     *
2502
     * @return array TOC as a table with 4 elements per row: title, link, status and level
2503
     */
2504
    public function get_toc()
2505
    {
2506
        $toc = [];
2507
        foreach ($this->ordered_items as $item_id) {
2508
            // TODO: Change this link generation and use new function instead.
2509
            $toc[] = [
2510
                'id' => $item_id,
2511
                'title' => $this->items[$item_id]->get_title(),
2512
                'status' => $this->items[$item_id]->get_status(),
2513
                'status_class' => self::getStatusCSSClassName($this->items[$item_id]->get_status()),
2514
                'level' => $this->items[$item_id]->get_level(),
2515
                'type' => $this->items[$item_id]->get_type(),
2516
                'description' => $this->items[$item_id]->get_description(),
2517
                'path' => $this->items[$item_id]->get_path(),
2518
                'parent' => $this->items[$item_id]->get_parent(),
2519
            ];
2520
        }
2521
2522
        return $toc;
2523
    }
2524
2525
    /**
2526
     * Returns the CSS class name associated with a given item status.
2527
     *
2528
     * @param $status string an item status
2529
     *
2530
     * @return string CSS class name
2531
     */
2532
    public static function getStatusCSSClassName($status)
2533
    {
2534
        if (array_key_exists($status, self::STATUS_CSS_CLASS_NAME)) {
2535
            return self::STATUS_CSS_CLASS_NAME[$status];
2536
        }
2537
2538
        return '';
2539
    }
2540
2541
    /**
2542
     * Generate and return the table of contents for this learnpath. The JS
2543
     * table returned is used inside of scorm_api.php.
2544
     *
2545
     * @param string $varname
2546
     *
2547
     * @return string A JS array variable construction
2548
     */
2549
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
2550
    {
2551
        $toc = $varname.' = new Array();';
2552
        foreach ($this->ordered_items as $item_id) {
2553
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
2554
        }
2555
2556
        return $toc;
2557
    }
2558
2559
    /**
2560
     * Gets the learning path type.
2561
     *
2562
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
2563
     *
2564
     * @return mixed Type ID or name, depending on the parameter
2565
     */
2566
    public function get_type($get_name = false)
2567
    {
2568
        $res = false;
2569
        if (!empty($this->type) && (!$get_name)) {
2570
            $res = $this->type;
2571
        }
2572
2573
        return $res;
2574
    }
2575
2576
    /**
2577
     * Gets the learning path type as static method.
2578
     *
2579
     * @param int $lp_id
2580
     *
2581
     * @return mixed Type ID or name, depending on the parameter
2582
     */
2583
    public static function get_type_static($lp_id = 0)
2584
    {
2585
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
2586
        $lp_id = (int) $lp_id;
2587
        $sql = "SELECT lp_type FROM $tbl_lp
2588
                WHERE iid = $lp_id";
2589
        $res = Database::query($sql);
2590
        if (false === $res) {
2591
            return null;
2592
        }
2593
        if (Database::num_rows($res) <= 0) {
2594
            return null;
2595
        }
2596
        $row = Database::fetch_array($res);
2597
2598
        return $row['lp_type'];
2599
    }
2600
2601
    /**
2602
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
2603
     * This method can be used as abstract and is recursive.
2604
     *
2605
     * @param CLp $lp
2606
     * @param int $parent    Parent ID of the items to look for
2607
     *
2608
     * @return array Ordered list of item IDs (empty array on error)
2609
     */
2610
    public static function get_flat_ordered_items_list(CLp $lp, $parent = 0)
2611
    {
2612
        $parent = (int) $parent;
2613
2614
        $criteria = Criteria::create()
2615
            ->orderBy(
2616
                [
2617
                    'displayOrder' => Criteria::ASC,
2618
                ]
2619
            );
2620
        $items = $lp->getItems()->matching($criteria);
2621
        $items = $items->filter(
2622
            function (CLpItem $element) use ($parent) {
2623
                if (empty($parent)) {
2624
                    $parent = null;
2625
                    return $element->getParent() === $parent;
2626
                } else {
2627
                    if (null !== $element->getParent()) {
2628
                        return $element->getParent()->getIid() === $parent;
2629
                    }
2630
                    return false;
2631
                }
2632
            }
2633
        );
2634
2635
        /*
2636
         $lp = (int) $lp;
2637
        $parent = (int) $parent;
2638
2639
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2640
        $sql = "SELECT iid FROM $tbl_lp_item
2641
                WHERE lp_id = $lp AND parent_item_id = $parent
2642
                ORDER BY display_order";
2643
2644
        $res = Database::query($sql);
2645
        while ($row = Database::fetch_array($res)) {
2646
            $sublist = self::get_flat_ordered_items_list(
2647
                $lp,
2648
                $row['iid'],
2649
                $course_id
2650
            );
2651
            $list[] = $row['iid'];
2652
            foreach ($sublist as $item) {
2653
                $list[] = $item;
2654
            }
2655
        }
2656
        */
2657
2658
        $list = [];
2659
        foreach ($items as $item) {
2660
            $itemId = $item->getIid();
2661
            $sublist = self::get_flat_ordered_items_list($lp, $itemId);
2662
            $list[] = $itemId;
2663
            foreach ($sublist as $subItem) {
2664
                $list[] = $subItem;
2665
            }
2666
        }
2667
2668
        return $list;
2669
    }
2670
2671
    public static function getChapterTypes(): array
2672
    {
2673
        return [
2674
            'dir',
2675
        ];
2676
    }
2677
2678
    /**
2679
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
2680
     *
2681
     * @param array $toc_list
2682
     *
2683
     * @return array HTML TOC ready to display
2684
     */
2685
    public function getListArrayToc($toc_list = [])
2686
    {
2687
        $lpItemRepo = Container::getLpItemRepository();
2688
        $itemRoot = $lpItemRepo->getItemRoot($this->get_id());
2689
        $options = [
2690
            'decorate' => false,
2691
        ];
2692
        $list = $lpItemRepo->childrenHierarchy($itemRoot, false, $options);
2693
2694
        return $list;
2695
2696
2697
        if (empty($toc_list)) {
0 ignored issues
show
Unused Code introduced by
IfNode 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...
2698
            $toc_list = $this->get_toc();
2699
        }
2700
        // Temporary variables.
2701
        $currentItemId = $this->get_current_item_id();
2702
        $list = [];
2703
        $arrayList = [];
2704
2705
        foreach ($toc_list as $item) {
2706
            $list['id'] = $item['id'];
2707
            $list['status'] = $item['status'];
2708
            $cssStatus = null;
2709
2710
            if (array_key_exists($item['status'], self::STATUS_CSS_CLASS_NAME)) {
2711
                $cssStatus = self::STATUS_CSS_CLASS_NAME[$item['status']];
2712
            }
2713
2714
            $classStyle = ' ';
2715
            $dirTypes = self::getChapterTypes();
2716
2717
            if (in_array($item['type'], $dirTypes)) {
2718
                $classStyle = 'scorm_item_section ';
2719
            }
2720
            if ($item['id'] == $this->current) {
2721
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
2722
            } elseif (!in_array($item['type'], $dirTypes)) {
2723
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
2724
            }
2725
            $title = $item['title'];
2726
            if (empty($title)) {
2727
                $title = self::rl_get_resource_name(
2728
                    api_get_course_id(),
2729
                    $this->get_id(),
2730
                    $item['id']
2731
                );
2732
            }
2733
            $title = Security::remove_XSS($item['title']);
2734
2735
            if (empty($item['description'])) {
2736
                $list['description'] = $title;
2737
            } else {
2738
                $list['description'] = $item['description'];
2739
            }
2740
2741
            $list['class'] = $classStyle.' '.$cssStatus;
2742
            $list['level'] = $item['level'];
2743
            $list['type'] = $item['type'];
2744
2745
            if (in_array($item['type'], $dirTypes)) {
2746
                $list['css_level'] = 'level_'.$item['level'];
2747
            } else {
2748
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
2749
            }
2750
2751
            if (in_array($item['type'], $dirTypes)) {
2752
                $list['title'] = stripslashes($title);
2753
            } else {
2754
                $list['title'] = stripslashes($title);
2755
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
2756
                $list['current_id'] = $currentItemId;
2757
            }
2758
            $arrayList[] = $list;
2759
        }
2760
2761
        return $arrayList;
2762
    }
2763
2764
    /**
2765
     * Returns an HTML-formatted string ready to display with teacher buttons
2766
     * in LP view menu.
2767
     *
2768
     * @return string HTML TOC ready to display
2769
     */
2770
    public function get_teacher_toc_buttons()
2771
    {
2772
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
2773
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
2774
        $html = '';
2775
        if ($isAllow && false == $hideIcons) {
2776
            if ($this->get_lp_session_id() == api_get_session_id()) {
2777
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
2778
                $html .= '<div class="flex flex-row justify-center">';
2779
                $html .= "<a
2780
                    class='btn btn-sm btn-default'
2781
                    href='lp_controller.php?".api_get_cidreq()."&action=build&lp_id=".$this->lp_id."&isStudentView=false'
2782
                    target='_parent'>".
2783
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
2784
                $html .= "<a
2785
                    class='btn btn-sm btn-default'
2786
                    href='lp_controller.php?".api_get_cidreq()."&action=add_item&type=step&lp_id=".$this->lp_id."&isStudentView=false'
2787
                    target='_parent'>".
2788
                    Display::returnFontAwesomeIcon('pencil-alt').get_lang('Edit')."</a>";
2789
                $html .= '<a
2790
                    class="btn btn-sm btn-default"
2791
                    href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
2792
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
2793
                $html .= '</div>';
2794
                $html .= '</div>';
2795
            }
2796
        }
2797
2798
        return $html;
2799
    }
2800
2801
    /**
2802
     * Gets the learnpath name/title.
2803
     *
2804
     * @return string Learnpath name/title
2805
     */
2806
    public function get_name()
2807
    {
2808
        if (!empty($this->name)) {
2809
            return $this->name;
2810
        }
2811
2812
        return 'N/A';
2813
    }
2814
2815
    /**
2816
     * @return string
2817
     */
2818
    public function getNameNoTags()
2819
    {
2820
        return strip_tags($this->get_name());
2821
    }
2822
2823
    /**
2824
     * Gets a link to the resource from the present location, depending on item ID.
2825
     *
2826
     * @param string $type         Type of link expected
2827
     * @param int    $item_id      Learnpath item ID
2828
     * @param bool   $provided_toc
2829
     *
2830
     * @return string $provided_toc Link to the lp_item resource
2831
     */
2832
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
2833
    {
2834
        $course_id = $this->get_course_int_id();
2835
        $item_id = (int) $item_id;
2836
2837
        if (empty($item_id)) {
2838
            $item_id = $this->get_current_item_id();
2839
2840
            if (empty($item_id)) {
2841
                //still empty, this means there was no item_id given and we are not in an object context or
2842
                //the object property is empty, return empty link
2843
                $this->first();
2844
2845
                return '';
2846
            }
2847
        }
2848
2849
        $file = '';
2850
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
2851
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
2852
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2853
2854
        $sql = "SELECT
2855
                    l.lp_type as ltype,
2856
                    l.path as lpath,
2857
                    li.item_type as litype,
2858
                    li.path as lipath,
2859
                    li.parameters as liparams
2860
        		FROM $lp_table l
2861
                INNER JOIN $lp_item_table li
2862
                ON (li.lp_id = l.iid)
2863
        		WHERE
2864
        		    li.iid = $item_id
2865
        		";
2866
        $res = Database::query($sql);
2867
        if (Database::num_rows($res) > 0) {
2868
            $row = Database::fetch_array($res);
2869
            $lp_type = $row['ltype'];
2870
            $lp_path = $row['lpath'];
2871
            $lp_item_type = $row['litype'];
2872
            $lp_item_path = $row['lipath'];
2873
            $lp_item_params = $row['liparams'];
2874
            if (empty($lp_item_params) && false !== strpos($lp_item_path, '?')) {
2875
                [$lp_item_path, $lp_item_params] = explode('?', $lp_item_path);
2876
            }
2877
            //$sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
2878
            if ('http' === $type) {
2879
                //web path
2880
                //$course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
2881
            } else {
2882
                //$course_path = $sys_course_path; //system path
2883
            }
2884
2885
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
2886
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
2887
            if (in_array(
2888
                $lp_item_type,
2889
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
2890
            )
2891
            ) {
2892
                $lp_type = CLp::LP_TYPE;
2893
            }
2894
2895
            // Now go through the specific cases to get the end of the path
2896
            // @todo Use constants instead of int values.
2897
            switch ($lp_type) {
2898
                case CLp::LP_TYPE:
2899
                    $file = self::rl_get_resource_link_for_learnpath(
2900
                        $course_id,
2901
                        $this->get_id(),
2902
                        $item_id,
2903
                        $this->get_view_id()
2904
                    );
2905
                    switch ($lp_item_type) {
2906
                        case 'document':
2907
                            // Shows a button to download the file instead of just downloading the file directly.
2908
                            $documentPathInfo = pathinfo($file);
2909
                            if (isset($documentPathInfo['extension'])) {
2910
                                $parsed = parse_url($documentPathInfo['extension']);
2911
                                if (isset($parsed['path'])) {
2912
                                    $extension = $parsed['path'];
2913
                                    $extensionsToDownload = [
2914
                                        'zip',
2915
                                        'ppt',
2916
                                        'pptx',
2917
                                        'ods',
2918
                                        'xlsx',
2919
                                        'xls',
2920
                                        'csv',
2921
                                        'doc',
2922
                                        'docx',
2923
                                        'dot',
2924
                                    ];
2925
2926
                                    if (in_array($extension, $extensionsToDownload)) {
2927
                                        $file = api_get_path(WEB_CODE_PATH).
2928
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
2929
                                    }
2930
                                }
2931
                            }
2932
                            break;
2933
                        case 'dir':
2934
                            $file = 'lp_content.php?type=dir';
2935
                            break;
2936
                        case 'link':
2937
                            if (Link::is_youtube_link($file)) {
2938
                                $src = Link::get_youtube_video_id($file);
2939
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
2940
                            } elseif (Link::isVimeoLink($file)) {
2941
                                $src = Link::getVimeoLinkId($file);
2942
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
2943
                            } else {
2944
                                // If the current site is HTTPS and the link is
2945
                                // HTTP, browsers will refuse opening the link
2946
                                $urlId = api_get_current_access_url_id();
2947
                                $url = api_get_access_url($urlId, false);
2948
                                $protocol = substr($url['url'], 0, 5);
2949
                                if ('https' === $protocol) {
2950
                                    $linkProtocol = substr($file, 0, 5);
2951
                                    if ('http:' === $linkProtocol) {
2952
                                        //this is the special intervention case
2953
                                        $file = api_get_path(WEB_CODE_PATH).
2954
                                            'lp/embed.php?type=nonhttps&source='.urlencode($file);
2955
                                    }
2956
                                }
2957
                            }
2958
                            break;
2959
                        case 'quiz':
2960
                            // Check how much attempts of a exercise exits in lp
2961
                            $lp_item_id = $this->get_current_item_id();
2962
                            $lp_view_id = $this->get_view_id();
2963
2964
                            $prevent_reinit = null;
2965
                            if (isset($this->items[$this->current])) {
2966
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
2967
                            }
2968
2969
                            if (empty($provided_toc)) {
2970
                                $list = $this->get_toc();
2971
                            } else {
2972
                                $list = $provided_toc;
2973
                            }
2974
2975
                            $type_quiz = false;
2976
                            foreach ($list as $toc) {
2977
                                if ($toc['id'] == $lp_item_id && 'quiz' === $toc['type']) {
2978
                                    $type_quiz = true;
2979
                                }
2980
                            }
2981
2982
                            if ($type_quiz) {
2983
                                $lp_item_id = (int) $lp_item_id;
2984
                                $lp_view_id = (int) $lp_view_id;
2985
                                $sql = "SELECT count(*) FROM $lp_item_view_table
2986
                                        WHERE
2987
                                            lp_item_id='".$lp_item_id."' AND
2988
                                            lp_view_id ='".$lp_view_id."' AND
2989
                                            status='completed'";
2990
                                $result = Database::query($sql);
2991
                                $row_count = Database:: fetch_row($result);
2992
                                $count_item_view = (int) $row_count[0];
2993
                                $not_multiple_attempt = 0;
2994
                                if (1 === $prevent_reinit && $count_item_view > 0) {
2995
                                    $not_multiple_attempt = 1;
2996
                                }
2997
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
2998
                            }
2999
                            break;
3000
                    }
3001
3002
                    $tmp_array = explode('/', $file);
3003
                    $document_name = $tmp_array[count($tmp_array) - 1];
3004
                    if (strpos($document_name, '_DELETED_')) {
3005
                        $file = 'blank.php?error=document_deleted';
3006
                    }
3007
                    break;
3008
                case CLp::SCORM_TYPE:
3009
                    if ('dir' !== $lp_item_type) {
3010
                        // Quite complex here:
3011
                        // We want to make sure 'http://' (and similar) links can
3012
                        // be loaded as is (withouth the Chamilo path in front) but
3013
                        // some contents use this form: resource.htm?resource=http://blablabla
3014
                        // which means we have to find a protocol at the path's start, otherwise
3015
                        // it should not be considered as an external URL.
3016
                        // if ($this->prerequisites_match($item_id)) {
3017
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
3018
                            if ($this->debug > 2) {
3019
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3020
                            }
3021
                            // Distant url, return as is.
3022
                            $file = $lp_item_path;
3023
                        } else {
3024
                            if ($this->debug > 2) {
3025
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path);
3026
                            }
3027
                            // Prevent getting untranslatable urls.
3028
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3029
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3030
3031
                            /*$asset = $this->getEntity()->getAsset();
3032
                            $folder = Container::getAssetRepository()->getFolder($asset);
3033
                            $hasFile = Container::getAssetRepository()->getFileSystem()->has($folder.$lp_item_path);
3034
                            $file = null;
3035
                            if ($hasFile) {
3036
                                $file = Container::getAssetRepository()->getAssetUrl($asset).'/'.$lp_item_path;
3037
                            }*/
3038
                            $file = $this->scormUrl.$lp_item_path;
3039
3040
                            // Prepare the path.
3041
                            /*$file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3042
                            // TODO: Fix this for urls with protocol header.
3043
                            $file = str_replace('//', '/', $file);
3044
                            $file = str_replace(':/', '://', $file);
3045
                            if ('/' === substr($lp_path, -1)) {
3046
                                $lp_path = substr($lp_path, 0, -1);
3047
                            }*/
3048
                            /*if (!$hasFile) {
3049
                                // if file not found.
3050
                                $decoded = html_entity_decode($lp_item_path);
3051
                                [$decoded] = explode('?', $decoded);
3052
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3053
                                    $file = self::rl_get_resource_link_for_learnpath(
3054
                                        $course_id,
3055
                                        $this->get_id(),
3056
                                        $item_id,
3057
                                        $this->get_view_id()
3058
                                    );
3059
                                    if (empty($file)) {
3060
                                        $file = 'blank.php?error=document_not_found';
3061
                                    } else {
3062
                                        $tmp_array = explode('/', $file);
3063
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3064
                                        if (strpos($document_name, '_DELETED_')) {
3065
                                            $file = 'blank.php?error=document_deleted';
3066
                                        } else {
3067
                                            $file = 'blank.php?error=document_not_found';
3068
                                        }
3069
                                    }
3070
                                } else {
3071
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3072
                                }
3073
                            }*/
3074
                        }
3075
3076
                        // We want to use parameters if they were defined in the imsmanifest
3077
                        if (false === strpos($file, 'blank.php')) {
3078
                            $lp_item_params = ltrim($lp_item_params, '?');
3079
                            $file .= (false === strstr($file, '?') ? '?' : '').$lp_item_params;
3080
                        }
3081
                    } else {
3082
                        $file = 'lp_content.php?type=dir';
3083
                    }
3084
                    break;
3085
                case CLp::AICC_TYPE:
3086
                    // Formatting AICC HACP append URL.
3087
                    $aicc_append = '?aicc_sid='.
3088
                        urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3089
                    if (!empty($lp_item_params)) {
3090
                        $aicc_append .= $lp_item_params.'&';
3091
                    }
3092
                    if ('dir' !== $lp_item_type) {
3093
                        // Quite complex here:
3094
                        // We want to make sure 'http://' (and similar) links can
3095
                        // be loaded as is (withouth the Chamilo path in front) but
3096
                        // some contents use this form: resource.htm?resource=http://blablabla
3097
                        // which means we have to find a protocol at the path's start, otherwise
3098
                        // it should not be considered as an external URL.
3099
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
3100
                            if ($this->debug > 2) {
3101
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3102
                            }
3103
                            // Distant url, return as is.
3104
                            $file = $lp_item_path;
3105
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3106
                            /*
3107
                            if (stristr($file,'<servername>') !== false) {
3108
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3109
                            }
3110
                            */
3111
                            if (false !== stripos($file, '<servername>')) {
3112
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3113
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3114
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3115
                            }
3116
3117
                            $file .= $aicc_append;
3118
                        } else {
3119
                            if ($this->debug > 2) {
3120
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3121
                            }
3122
                            // Prevent getting untranslatable urls.
3123
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3124
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3125
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3126
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3127
                            // TODO: Fix this for urls with protocol header.
3128
                            $file = str_replace('//', '/', $file);
3129
                            $file = str_replace(':/', '://', $file);
3130
                            $file .= $aicc_append;
3131
                        }
3132
                    } else {
3133
                        $file = 'lp_content.php?type=dir';
3134
                    }
3135
                    break;
3136
                case 4:
3137
                default:
3138
                    break;
3139
            }
3140
            // Replace &amp; by & because &amp; will break URL with params
3141
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3142
        }
3143
        if ($this->debug > 2) {
3144
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3145
        }
3146
3147
        return $file;
3148
    }
3149
3150
    /**
3151
     * Gets the latest usable view or generate a new one.
3152
     *
3153
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3154
     * @param int $userId      The user ID, as $this->get_user_id() is not always available
3155
     *
3156
     * @return int DB lp_view id
3157
     */
3158
    public function get_view($attempt_num = 0, $userId = null)
3159
    {
3160
        $search = '';
3161
        $attempt_num = (int) $attempt_num;
3162
        // Use $attempt_num to enable multi-views management (disabled so far).
3163
        if (!empty($attempt_num)) {
3164
            $search = 'AND view_count = '.$attempt_num;
3165
        }
3166
3167
        $course_id = api_get_course_int_id();
3168
        $sessionId = api_get_session_id();
3169
3170
        // Check user ID.
3171
        if (empty($userId)) {
3172
            if (empty($this->get_user_id())) {
3173
                $this->error = 'User ID is empty in learnpath::get_view()';
3174
3175
                return null;
3176
            } else {
3177
                $userId = $this->get_user_id();
3178
            }
3179
        }
3180
        $sessionCondition = api_get_session_condition($sessionId);
3181
3182
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3183
        $table = Database::get_course_table(TABLE_LP_VIEW);
3184
        $sql = "SELECT iid FROM $table
3185
        		WHERE
3186
        		    c_id = $course_id AND
3187
        		    lp_id = ".$this->get_id()." AND
3188
        		    user_id = ".$userId."
3189
        		    $sessionCondition
3190
        		    $search
3191
                ORDER BY view_count DESC";
3192
        $res = Database::query($sql);
3193
        if (Database::num_rows($res) > 0) {
3194
            $row = Database::fetch_array($res);
3195
            $this->lp_view_id = $row['iid'];
3196
        } elseif (!api_is_invitee()) {
3197
            $params = [
3198
                'c_id' => $course_id,
3199
                'lp_id' => $this->get_id(),
3200
                'user_id' => $this->get_user_id(),
3201
                'view_count' => 1,
3202
                'last_item' => 0,
3203
            ];
3204
            if (!empty($sessionId)) {
3205
                $params['session_id']  = $sessionId;
3206
            }
3207
            $this->lp_view_id = Database::insert($table, $params);
3208
        }
3209
3210
        return $this->lp_view_id;
3211
    }
3212
3213
    /**
3214
     * Gets the current view id.
3215
     *
3216
     * @return int View ID (from lp_view)
3217
     */
3218
    public function get_view_id()
3219
    {
3220
        if (!empty($this->lp_view_id)) {
3221
            return (int) $this->lp_view_id;
3222
        }
3223
3224
        return 0;
3225
    }
3226
3227
    /**
3228
     * Gets the update queue.
3229
     *
3230
     * @return array Array containing IDs of items to be updated by JavaScript
3231
     */
3232
    public function get_update_queue()
3233
    {
3234
        return $this->update_queue;
3235
    }
3236
3237
    /**
3238
     * Gets the user ID.
3239
     *
3240
     * @return int User ID
3241
     */
3242
    public function get_user_id()
3243
    {
3244
        if (!empty($this->user_id)) {
3245
            return (int) $this->user_id;
3246
        }
3247
3248
        return false;
3249
    }
3250
3251
    /**
3252
     * Checks if any of the items has an audio element attached.
3253
     *
3254
     * @return bool True or false
3255
     */
3256
    public function has_audio()
3257
    {
3258
        $has = false;
3259
        foreach ($this->items as $i => $item) {
3260
            if (!empty($this->items[$i]->audio)) {
3261
                $has = true;
3262
                break;
3263
            }
3264
        }
3265
3266
        return $has;
3267
    }
3268
3269
    /**
3270
     * Moves an item up and down at its level.
3271
     *
3272
     * @param int    $id        Item to move up and down
3273
     * @param string $direction Direction 'up' or 'down'
3274
     *
3275
     * @return bool|int
3276
     */
3277
    public function move_item($id, $direction)
3278
    {
3279
        $course_id = api_get_course_int_id();
3280
        if (empty($id) || empty($direction)) {
3281
            return false;
3282
        }
3283
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3284
        $sql_sel = "SELECT *
3285
                    FROM $tbl_lp_item
3286
                    WHERE
3287
                        iid = $id
3288
                    ";
3289
        $res_sel = Database::query($sql_sel);
3290
        // Check if elem exists.
3291
        if (Database::num_rows($res_sel) < 1) {
3292
            return false;
3293
        }
3294
        // Gather data.
3295
        $row = Database::fetch_array($res_sel);
3296
        $previous = $row['previous_item_id'];
3297
        $next = $row['next_item_id'];
3298
        $display = $row['display_order'];
3299
        $parent = $row['parent_item_id'];
3300
        $lp = $row['lp_id'];
3301
        // Update the item (switch with previous/next one).
3302
        switch ($direction) {
3303
            case 'up':
3304
                if ($display > 1) {
3305
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3306
                                 WHERE iid = $previous";
3307
                    $res_sel2 = Database::query($sql_sel2);
3308
                    if (Database::num_rows($res_sel2) < 1) {
3309
                        $previous_previous = 0;
3310
                    }
3311
                    // Gather data.
3312
                    $row2 = Database::fetch_array($res_sel2);
3313
                    $previous_previous = $row2['previous_item_id'];
3314
                    // Update previous_previous item (switch "next" with current).
3315
                    if (0 != $previous_previous) {
3316
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3317
                                        next_item_id = $id
3318
                                    WHERE iid = $previous_previous";
3319
                        Database::query($sql_upd2);
3320
                    }
3321
                    // Update previous item (switch with current).
3322
                    if (0 != $previous) {
3323
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3324
                                    next_item_id = $next,
3325
                                    previous_item_id = $id,
3326
                                    display_order = display_order +1
3327
                                    WHERE iid = $previous";
3328
                        Database::query($sql_upd2);
3329
                    }
3330
3331
                    // Update current item (switch with previous).
3332
                    if (0 != $id) {
3333
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3334
                                        next_item_id = $previous,
3335
                                        previous_item_id = $previous_previous,
3336
                                        display_order = display_order-1
3337
                                    WHERE c_id = ".$course_id." AND id = $id";
3338
                        Database::query($sql_upd2);
3339
                    }
3340
                    // Update next item (new previous item).
3341
                    if (!empty($next)) {
3342
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
3343
                                     WHERE iid = $next";
3344
                        Database::query($sql_upd2);
3345
                    }
3346
                    $display = $display - 1;
3347
                }
3348
                break;
3349
            case 'down':
3350
                if (0 != $next) {
3351
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3352
                                 WHERE iid = $next";
3353
                    $res_sel2 = Database::query($sql_sel2);
3354
                    if (Database::num_rows($res_sel2) < 1) {
3355
                        $next_next = 0;
3356
                    }
3357
                    // Gather data.
3358
                    $row2 = Database::fetch_array($res_sel2);
3359
                    $next_next = $row2['next_item_id'];
3360
                    // Update previous item (switch with current).
3361
                    if (0 != $previous) {
3362
                        $sql_upd2 = "UPDATE $tbl_lp_item
3363
                                     SET next_item_id = $next
3364
                                     WHERE iid = $previous";
3365
                        Database::query($sql_upd2);
3366
                    }
3367
                    // Update current item (switch with previous).
3368
                    if (0 != $id) {
3369
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3370
                                     previous_item_id = $next,
3371
                                     next_item_id = $next_next,
3372
                                     display_order = display_order + 1
3373
                                     WHERE iid = $id";
3374
                        Database::query($sql_upd2);
3375
                    }
3376
3377
                    // Update next item (new previous item).
3378
                    if (0 != $next) {
3379
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3380
                                     previous_item_id = $previous,
3381
                                     next_item_id = $id,
3382
                                     display_order = display_order-1
3383
                                     WHERE iid = $next";
3384
                        Database::query($sql_upd2);
3385
                    }
3386
3387
                    // Update next_next item (switch "previous" with current).
3388
                    if (0 != $next_next) {
3389
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3390
                                     previous_item_id = $id
3391
                                     WHERE iid = $next_next";
3392
                        Database::query($sql_upd2);
3393
                    }
3394
                    $display = $display + 1;
3395
                }
3396
                break;
3397
            default:
3398
                return false;
3399
        }
3400
3401
        return $display;
3402
    }
3403
3404
    /**
3405
     * Move a LP up (display_order).
3406
     *
3407
     * @param int $lp_id      Learnpath ID
3408
     * @param int $categoryId Category ID
3409
     *
3410
     * @return bool
3411
     */
3412
    public static function move_up($lp_id, $categoryId = 0)
3413
    {
3414
        $courseId = api_get_course_int_id();
3415
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3416
3417
        $categoryCondition = '';
3418
        if (!empty($categoryId)) {
3419
            $categoryId = (int) $categoryId;
3420
            $categoryCondition = " AND category_id = $categoryId";
3421
        }
3422
        $sql = "SELECT * FROM $lp_table
3423
                WHERE c_id = $courseId
3424
                $categoryCondition
3425
                ORDER BY display_order";
3426
        $res = Database::query($sql);
3427
        if (false === $res) {
3428
            return false;
3429
        }
3430
3431
        $lps = [];
3432
        $lp_order = [];
3433
        $num = Database::num_rows($res);
3434
        // First check the order is correct, globally (might be wrong because
3435
        // of versions < 1.8.4)
3436
        if ($num > 0) {
3437
            $i = 1;
3438
            while ($row = Database::fetch_array($res)) {
3439
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
3440
                    $sql = "UPDATE $lp_table SET display_order = $i
3441
                            WHERE iid = ".$row['iid'];
3442
                    Database::query($sql);
3443
                }
3444
                $row['display_order'] = $i;
3445
                $lps[$row['iid']] = $row;
3446
                $lp_order[$i] = $row['iid'];
3447
                $i++;
3448
            }
3449
        }
3450
        if ($num > 1) { // If there's only one element, no need to sort.
3451
            $order = $lps[$lp_id]['display_order'];
3452
            if ($order > 1) { // If it's the first element, no need to move up.
3453
                $sql = "UPDATE $lp_table SET display_order = $order
3454
                        WHERE iid = ".$lp_order[$order - 1];
3455
                Database::query($sql);
3456
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
3457
                        WHERE iid = $lp_id";
3458
                Database::query($sql);
3459
            }
3460
        }
3461
3462
        return true;
3463
    }
3464
3465
    /**
3466
     * Move a learnpath down (display_order).
3467
     *
3468
     * @param int $lp_id      Learnpath ID
3469
     * @param int $categoryId Category ID
3470
     *
3471
     * @return bool
3472
     */
3473
    public static function move_down($lp_id, $categoryId = 0)
3474
    {
3475
        $courseId = api_get_course_int_id();
3476
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3477
3478
        $categoryCondition = '';
3479
        if (!empty($categoryId)) {
3480
            $categoryId = (int) $categoryId;
3481
            $categoryCondition = " AND category_id = $categoryId";
3482
        }
3483
3484
        $sql = "SELECT * FROM $lp_table
3485
                WHERE c_id = $courseId
3486
                $categoryCondition
3487
                ORDER BY display_order";
3488
        $res = Database::query($sql);
3489
        if (false === $res) {
3490
            return false;
3491
        }
3492
        $lps = [];
3493
        $lp_order = [];
3494
        $num = Database::num_rows($res);
3495
        $max = 0;
3496
        // First check the order is correct, globally (might be wrong because
3497
        // of versions < 1.8.4).
3498
        if ($num > 0) {
3499
            $i = 1;
3500
            while ($row = Database::fetch_array($res)) {
3501
                $max = $i;
3502
                if ($row['display_order'] != $i) {
3503
                    // If we find a gap in the order, we need to fix it.
3504
                    $sql = "UPDATE $lp_table SET display_order = $i
3505
                              WHERE iid = ".$row['iid'];
3506
                    Database::query($sql);
3507
                }
3508
                $row['display_order'] = $i;
3509
                $lps[$row['iid']] = $row;
3510
                $lp_order[$i] = $row['iid'];
3511
                $i++;
3512
            }
3513
        }
3514
        if ($num > 1) { // If there's only one element, no need to sort.
3515
            $order = $lps[$lp_id]['display_order'];
3516
            if ($order < $max) { // If it's the first element, no need to move up.
3517
                $sql = "UPDATE $lp_table SET display_order = $order
3518
                        WHERE iid = ".$lp_order[$order + 1];
3519
                Database::query($sql);
3520
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
3521
                        WHERE iid = $lp_id";
3522
                Database::query($sql);
3523
            }
3524
        }
3525
3526
        return true;
3527
    }
3528
3529
    /**
3530
     * Updates learnpath attributes to point to the next element
3531
     * The last part is similar to set_current_item but processing the other way around.
3532
     */
3533
    public function next()
3534
    {
3535
        if ($this->debug > 0) {
3536
            error_log('In learnpath::next()', 0);
3537
        }
3538
        $this->last = $this->get_current_item_id();
3539
        $this->items[$this->last]->save(
3540
            false,
3541
            $this->prerequisites_match($this->last)
3542
        );
3543
        $this->autocomplete_parents($this->last);
3544
        $new_index = $this->get_next_index();
3545
        if ($this->debug > 2) {
3546
            error_log('New index: '.$new_index, 0);
3547
        }
3548
        $this->index = $new_index;
3549
        if ($this->debug > 2) {
3550
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
3551
        }
3552
        $this->current = $this->ordered_items[$new_index];
3553
        if ($this->debug > 2) {
3554
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
3555
        }
3556
    }
3557
3558
    /**
3559
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
3560
     * class, this might be redefined to allow several behaviours depending on the document type.
3561
     *
3562
     * @param int $id Resource ID
3563
     */
3564
    public function open($id)
3565
    {
3566
        // TODO:
3567
        // set the current resource attribute to this resource
3568
        // switch on element type (redefine in child class?)
3569
        // set status for this item to "opened"
3570
        // start timer
3571
        // initialise score
3572
        $this->index = 0; //or = the last item seen (see $this->last)
3573
    }
3574
3575
    /**
3576
     * Check that all prerequisites are fulfilled. Returns true and an
3577
     * empty string on success, returns false
3578
     * and the prerequisite string on error.
3579
     * This function is based on the rules for aicc_script language as
3580
     * described in the SCORM 1.2 CAM documentation page 108.
3581
     *
3582
     * @param int $itemId Optional item ID. If none given, uses the current open item.
3583
     *
3584
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
3585
     *              string otherwise
3586
     */
3587
    public function prerequisites_match($itemId = null)
3588
    {
3589
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
3590
        if ($allow) {
3591
            if (api_is_allowed_to_edit() ||
3592
                api_is_platform_admin(true) ||
3593
                api_is_drh() ||
3594
                api_is_coach(api_get_session_id(), api_get_course_int_id())
3595
            ) {
3596
                return true;
3597
            }
3598
        }
3599
3600
        $debug = $this->debug;
3601
        if ($debug > 0) {
3602
            error_log('In learnpath::prerequisites_match()');
3603
        }
3604
3605
        if (empty($itemId)) {
3606
            $itemId = $this->current;
3607
        }
3608
3609
        $currentItem = $this->getItem($itemId);
3610
3611
        if ($currentItem) {
3612
            if (2 == $this->type) {
3613
                // Getting prereq from scorm
3614
                $prereq_string = $this->get_scorm_prereq_string($itemId);
3615
            } else {
3616
                $prereq_string = $currentItem->get_prereq_string();
3617
            }
3618
3619
            if (empty($prereq_string)) {
3620
                if ($debug > 0) {
3621
                    error_log('Found prereq_string is empty return true');
3622
                }
3623
3624
                return true;
3625
            }
3626
3627
            // Clean spaces.
3628
            $prereq_string = str_replace(' ', '', $prereq_string);
3629
            if ($debug > 0) {
3630
                error_log('Found prereq_string: '.$prereq_string, 0);
3631
            }
3632
3633
            // Now send to the parse_prereq() function that will check this component's prerequisites.
3634
            $result = $currentItem->parse_prereq(
3635
                $prereq_string,
3636
                $this->items,
3637
                $this->refs_list,
3638
                $this->get_user_id()
3639
            );
3640
3641
            if (false === $result) {
3642
                $this->set_error_msg($currentItem->prereq_alert);
3643
            }
3644
        } else {
3645
            $result = true;
3646
            if ($debug > 1) {
3647
                error_log('$this->items['.$itemId.'] was not an object', 0);
3648
            }
3649
        }
3650
3651
        if ($debug > 1) {
3652
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
3653
        }
3654
3655
        return $result;
3656
    }
3657
3658
    /**
3659
     * Updates learnpath attributes to point to the previous element
3660
     * The last part is similar to set_current_item but processing the other way around.
3661
     */
3662
    public function previous()
3663
    {
3664
        $this->last = $this->get_current_item_id();
3665
        $this->items[$this->last]->save(
3666
            false,
3667
            $this->prerequisites_match($this->last)
3668
        );
3669
        $this->autocomplete_parents($this->last);
3670
        $new_index = $this->get_previous_index();
3671
        $this->index = $new_index;
3672
        $this->current = $this->ordered_items[$new_index];
3673
    }
3674
3675
    /**
3676
     * Publishes a learnpath. This basically means show or hide the learnpath
3677
     * to normal users.
3678
     * Can be used as abstract.
3679
     *
3680
     * @param int $id         Learnpath ID
3681
     * @param int $visibility New visibility (1 = visible/published, 0= invisible/draft)
3682
     *
3683
     * @return bool
3684
     */
3685
    public static function toggleVisibility($id, $visibility = 1)
3686
    {
3687
        $repo = Container::getLpRepository();
3688
        $lp = $repo->find($id);
3689
3690
        if (!$lp) {
3691
            return false;
3692
        }
3693
3694
        $visibility = (int) $visibility;
3695
3696
        if (1 === $visibility) {
3697
            $repo->setVisibilityPublished($lp);
3698
        } else {
3699
            $repo->setVisibilityDraft($lp);
3700
        }
3701
3702
        return true;
3703
3704
        /*$action = 'visible';
3705
        if (1 != $set_visibility) {
3706
            $action = 'invisible';
3707
            self::toggle_publish($lp_id, 'i');
3708
        }
3709
3710
        return api_item_property_update(
3711
            api_get_course_info(),
3712
            TOOL_LEARNPATH,
3713
            $lp_id,
3714
            $action,
3715
            api_get_user_id()
3716
        );*/
3717
    }
3718
3719
    /**
3720
     * Publishes a learnpath category.
3721
     * This basically means show or hide the learnpath category to normal users.
3722
     *
3723
     * @param int $id
3724
     * @param int $visibility
3725
     *
3726
     * @return bool
3727
     */
3728
    public static function toggleCategoryVisibility($id, $visibility = 1)
3729
    {
3730
        $repo = Container::getLpCategoryRepository();
3731
        $resource = $repo->find($id);
3732
3733
        if (!$resource) {
3734
            return false;
3735
        }
3736
3737
        $visibility = (int) $visibility;
3738
3739
        if (1 === $visibility) {
3740
            $repo->setVisibilityPublished($resource);
3741
        } else {
3742
            $repo->setVisibilityDraft($resource);
3743
            self::toggleCategoryPublish($id, 0);
3744
        }
3745
3746
        return false;
3747
        /*
3748
        $action = 'visible';
3749
        if (1 != $visibility) {
3750
            self::toggleCategoryPublish($id, 0);
3751
            $action = 'invisible';
3752
        }
3753
3754
        return api_item_property_update(
3755
            api_get_course_info(),
3756
            TOOL_LEARNPATH_CATEGORY,
3757
            $id,
3758
            $action,
3759
            api_get_user_id()
3760
        );*/
3761
    }
3762
3763
    /**
3764
     * Publishes a learnpath. This basically means show or hide the learnpath
3765
     * on the course homepage.
3766
     *
3767
     * @param int    $id            Learnpath id
3768
     * @param string $setVisibility New visibility (v/i - visible/invisible)
3769
     *
3770
     * @return bool
3771
     */
3772
    public static function togglePublish($id, $setVisibility = 'v')
3773
    {
3774
        $addShortcut = false;
3775
        if ('v' === $setVisibility) {
3776
            $addShortcut = true;
3777
        }
3778
        $repo = Container::getLpRepository();
3779
        /** @var CLp $lp */
3780
        $lp = $repo->find($id);
3781
        if (null === $lp) {
3782
            return false;
3783
        }
3784
        $repoShortcut = Container::getShortcutRepository();
3785
        $courseEntity = api_get_course_entity();
3786
3787
        if ($addShortcut) {
3788
            $repoShortcut->addShortCut($lp, $courseEntity, $courseEntity, api_get_session_entity());
3789
        } else {
3790
            $repoShortcut->removeShortCut($lp);
3791
        }
3792
3793
        return true;
3794
3795
        /*
3796
        $course_id = api_get_course_int_id();
3797
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3798
        $lp_id = (int) $lp_id;
3799
        $sql = "SELECT * FROM $tbl_lp
3800
                WHERE iid = $lp_id";
3801
        $result = Database::query($sql);
3802
3803
        if (Database::num_rows($result)) {
3804
            $row = Database::fetch_array($result);
3805
            $name = Database::escape_string($row['name']);
3806
            if ($set_visibility == 'i') {
3807
                $v = 0;
3808
            }
3809
            if ($set_visibility == 'v') {
3810
                $v = 1;
3811
            }
3812
3813
            $session_id = api_get_session_id();
3814
            $session_condition = api_get_session_condition($session_id);
3815
3816
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
3817
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
3818
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
3819
3820
            $sql = "SELECT * FROM $tbl_tool
3821
                    WHERE
3822
                        c_id = $course_id AND
3823
                        (link = '$link' OR link = '$oldLink') AND
3824
                        image = 'scormbuilder.gif' AND
3825
                        (
3826
                            link LIKE '$link%' OR
3827
                            link LIKE '$oldLink%'
3828
                        )
3829
                        $session_condition
3830
                    ";
3831
3832
            $result = Database::query($sql);
3833
            $num = Database::num_rows($result);
3834
            if ($set_visibility == 'i' && $num > 0) {
3835
                $sql = "DELETE FROM $tbl_tool
3836
                        WHERE
3837
                            c_id = $course_id AND
3838
                            (link = '$link' OR link = '$oldLink') AND
3839
                            image='scormbuilder.gif'
3840
                            $session_condition";
3841
                Database::query($sql);
3842
            } elseif ($set_visibility == 'v' && $num == 0) {
3843
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
3844
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
3845
                Database::query($sql);
3846
                $insertId = Database::insert_id();
3847
                if ($insertId) {
3848
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
3849
                    Database::query($sql);
3850
                }
3851
            } elseif ($set_visibility == 'v' && $num > 0) {
3852
                $sql = "UPDATE $tbl_tool SET
3853
                            c_id = $course_id,
3854
                            name = '$name',
3855
                            link = '$link',
3856
                            image = 'scormbuilder.gif',
3857
                            visibility = '$v',
3858
                            admin = '0',
3859
                            address = 'pastillegris.gif',
3860
                            added_tool = 0,
3861
                            session_id = $session_id
3862
                        WHERE
3863
                            c_id = ".$course_id." AND
3864
                            (link = '$link' OR link = '$oldLink') AND
3865
                            image='scormbuilder.gif'
3866
                            $session_condition
3867
                        ";
3868
                Database::query($sql);
3869
            } else {
3870
                // Parameter and database incompatible, do nothing, exit.
3871
                return false;
3872
            }
3873
        } else {
3874
            return false;
3875
        }*/
3876
    }
3877
3878
    /**
3879
     * Show or hide the learnpath category on the course homepage.
3880
     *
3881
     * @param int $id
3882
     * @param int $setVisibility
3883
     *
3884
     * @return bool
3885
     */
3886
    public static function toggleCategoryPublish($id, $setVisibility = 1)
3887
    {
3888
        $setVisibility = (int) $setVisibility;
3889
        $addShortcut = false;
3890
        if (1 === $setVisibility) {
3891
            $addShortcut = true;
3892
        }
3893
3894
        $repo = Container::getLpCategoryRepository();
3895
        /** @var CLpCategory $lp */
3896
        $category = $repo->find($id);
3897
3898
        if (null === $category) {
3899
            return false;
3900
        }
3901
3902
        $repoShortcut = Container::getShortcutRepository();
3903
        if ($addShortcut) {
3904
            $courseEntity = api_get_course_entity(api_get_course_int_id());
3905
            $repoShortcut->addShortCut($category, $courseEntity, $courseEntity, api_get_session_entity());
3906
        } else {
3907
            $repoShortcut->removeShortCut($category);
3908
        }
3909
3910
        return true;
3911
    }
3912
3913
    /**
3914
     * Check if the learnpath category is visible for a user.
3915
     *
3916
     * @return bool
3917
     */
3918
    public static function categoryIsVisibleForStudent(
3919
        CLpCategory $category,
3920
        User $user,
3921
        Course $course,
3922
        Session $session = null
3923
    ) {
3924
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
3925
3926
        if ($isAllowedToEdit) {
3927
            return true;
3928
        }
3929
3930
        $repo = Container::getLpCategoryRepository();
3931
        $categoryVisibility = $category->isVisible($course, $session);
3932
3933
        /*$categoryVisibility = api_get_item_visibility(
3934
            $courseInfo,
3935
            TOOL_LEARNPATH_CATEGORY,
3936
            $category->getId(),
3937
            $sessionId
3938
        );*/
3939
3940
        if (1 !== $categoryVisibility && -1 != $categoryVisibility) {
3941
            return false;
3942
        }
3943
3944
        $subscriptionSettings = self::getSubscriptionSettings();
3945
3946
        if (false == $subscriptionSettings['allow_add_users_to_lp_category']) {
3947
            return true;
3948
        }
3949
3950
        $noUserSubscribed = false;
3951
        $noGroupSubscribed = true;
3952
        $users = $category->getUsers();
3953
        if (empty($users) || !$users->count()) {
3954
            $noUserSubscribed = true;
3955
        } elseif ($category->hasUserAdded($user)) {
3956
            return true;
3957
        }
3958
3959
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
3960
        $em = Database::getManager();
3961
3962
        /** @var ItemPropertyRepository $itemRepo */
3963
        $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
3964
3965
        /** @var CourseRepository $courseRepo */
3966
        $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
3967
        $session = null;
3968
        if (!empty($sessionId)) {
3969
            $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
3970
        }
3971
3972
        $course = $courseRepo->find($courseId);
3973
3974
        if (0 != $courseId) {
3975
            // Subscribed groups to a LP
3976
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
3977
                    TOOL_LEARNPATH_CATEGORY,
3978
                    $category->getId(),
3979
                    $course,
3980
                    $session
3981
                );
3982
        }
3983
3984
        if (!empty($subscribedGroupsInLp)) {
3985
            $noGroupSubscribed = false;
3986
            if (!empty($groups)) {
3987
                $groups = array_column($groups, 'iid');
3988
                /** @var CItemProperty $item */
3989
                foreach ($subscribedGroupsInLp as $item) {
3990
                    if ($item->getGroup() &&
3991
                        in_array($item->getGroup()->getId(), $groups)
3992
                    ) {
3993
                        return true;
3994
                    }
3995
                }
3996
            }
3997
        }
3998
        $response = $noGroupSubscribed && $noUserSubscribed;
3999
4000
        return $response;
4001
    }
4002
4003
    /**
4004
     * Check if a learnpath category is published as course tool.
4005
     *
4006
     * @param int $courseId
4007
     *
4008
     * @return bool
4009
     */
4010
    public static function categoryIsPublished(CLpCategory $category, $courseId)
4011
    {
4012
        return false;
4013
        $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...
4014
        $em = Database::getManager();
4015
4016
        $tools = $em
4017
            ->createQuery("
4018
                SELECT t FROM ChamiloCourseBundle:CTool t
4019
                WHERE t.course = :course AND
4020
                    t.name = :name AND
4021
                    t.image LIKE 'lp_category.%' AND
4022
                    t.link LIKE :link
4023
            ")
4024
            ->setParameters([
4025
                'course' => $courseId,
4026
                'name' => strip_tags($category->getName()),
4027
                'link' => "$link%",
4028
            ])
4029
            ->getResult();
4030
4031
        /** @var CTool $tool */
4032
        $tool = current($tools);
4033
4034
        return $tool ? $tool->getVisibility() : false;
4035
    }
4036
4037
    /**
4038
     * Restart the whole learnpath. Return the URL of the first element.
4039
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4040
     * To use a similar method  statically, use the create_new_attempt() method.
4041
     *
4042
     * @return bool
4043
     */
4044
    public function restart()
4045
    {
4046
        if ($this->debug > 0) {
4047
            error_log('In learnpath::restart()', 0);
4048
        }
4049
        // TODO
4050
        // Call autosave method to save the current progress.
4051
        //$this->index = 0;
4052
        if (api_is_invitee()) {
4053
            return false;
4054
        }
4055
        $session_id = api_get_session_id();
4056
        $course_id = api_get_course_int_id();
4057
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4058
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4059
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4060
        if ($this->debug > 2) {
4061
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4062
        }
4063
        Database::query($sql);
4064
        $view_id = Database::insert_id();
4065
4066
        if ($view_id) {
4067
            $this->lp_view_id = $view_id;
4068
            $this->attempt = $this->attempt + 1;
4069
        } else {
4070
            $this->error = 'Could not insert into item_view table...';
4071
4072
            return false;
4073
        }
4074
        $this->autocomplete_parents($this->current);
4075
        foreach ($this->items as $index => $dummy) {
4076
            $this->items[$index]->restart();
4077
            $this->items[$index]->set_lp_view($this->lp_view_id);
4078
        }
4079
        $this->first();
4080
4081
        return true;
4082
    }
4083
4084
    /**
4085
     * Saves the current item.
4086
     *
4087
     * @return bool
4088
     */
4089
    public function save_current()
4090
    {
4091
        $debug = $this->debug;
4092
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4093
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4094
        if ($debug) {
4095
            error_log('save_current() saving item '.$this->current, 0);
4096
            error_log(''.print_r($this->items, true), 0);
4097
        }
4098
        if (isset($this->items[$this->current]) &&
4099
            is_object($this->items[$this->current])
4100
        ) {
4101
            if ($debug) {
4102
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4103
            }
4104
4105
            $res = $this->items[$this->current]->save(
4106
                false,
4107
                $this->prerequisites_match($this->current)
4108
            );
4109
            $this->autocomplete_parents($this->current);
4110
            $status = $this->items[$this->current]->get_status();
4111
            $this->update_queue[$this->current] = $status;
4112
4113
            if ($debug) {
4114
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4115
            }
4116
4117
            return $res;
4118
        }
4119
4120
        return false;
4121
    }
4122
4123
    /**
4124
     * Saves the given item.
4125
     *
4126
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4127
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4128
     *
4129
     * @return bool
4130
     */
4131
    public function save_item($item_id = null, $from_outside = true)
4132
    {
4133
        $debug = $this->debug;
4134
        if ($debug) {
4135
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4136
        }
4137
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4138
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4139
        if (empty($item_id)) {
4140
            $item_id = (int) $_REQUEST['id'];
4141
        }
4142
4143
        if (empty($item_id)) {
4144
            $item_id = $this->get_current_item_id();
4145
        }
4146
        if (isset($this->items[$item_id]) &&
4147
            is_object($this->items[$item_id])
4148
        ) {
4149
            if ($debug) {
4150
                error_log('Object exists');
4151
            }
4152
4153
            // Saving the item.
4154
            $res = $this->items[$item_id]->save(
4155
                $from_outside,
4156
                $this->prerequisites_match($item_id)
4157
            );
4158
4159
            if ($debug) {
4160
                error_log('update_queue before:');
4161
                error_log(print_r($this->update_queue, 1));
4162
            }
4163
            $this->autocomplete_parents($item_id);
4164
4165
            $status = $this->items[$item_id]->get_status();
4166
            $this->update_queue[$item_id] = $status;
4167
4168
            if ($debug) {
4169
                error_log('get_status(): '.$status);
4170
                error_log('update_queue after:');
4171
                error_log(print_r($this->update_queue, 1));
4172
            }
4173
4174
            return $res;
4175
        }
4176
4177
        return false;
4178
    }
4179
4180
    /**
4181
     * Saves the last item seen's ID only in case.
4182
     */
4183
    public function save_last()
4184
    {
4185
        $course_id = api_get_course_int_id();
4186
        $debug = $this->debug;
4187
        if ($debug) {
4188
            error_log('In learnpath::save_last()', 0);
4189
        }
4190
        $session_condition = api_get_session_condition(
4191
            api_get_session_id(),
4192
            true,
4193
            false
4194
        );
4195
        $table = Database::get_course_table(TABLE_LP_VIEW);
4196
4197
        $userId = $this->get_user_id();
4198
        if (empty($userId)) {
4199
            $userId = api_get_user_id();
4200
            if ($debug) {
4201
                error_log('$this->get_user_id() was empty, used api_get_user_id() instead in '.__FILE__.' line '.__LINE__);
4202
            }
4203
        }
4204
        if (isset($this->current) && !api_is_invitee()) {
4205
            if ($debug) {
4206
                error_log('Saving current item ('.$this->current.') for later review', 0);
4207
            }
4208
            $sql = "UPDATE $table SET
4209
                        last_item = ".$this->get_current_item_id()."
4210
                    WHERE
4211
                        c_id = $course_id AND
4212
                        lp_id = ".$this->get_id()." AND
4213
                        user_id = ".$userId." ".$session_condition;
4214
4215
            if ($debug) {
4216
                error_log('Saving last item seen : '.$sql, 0);
4217
            }
4218
            Database::query($sql);
4219
        }
4220
4221
        if (!api_is_invitee()) {
4222
            // Save progress.
4223
            [$progress] = $this->get_progress_bar_text('%');
4224
            $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
4225
            $scoreAsProgress = $this->getUseScoreAsProgress();
4226
            if ($scoreAsProgress && $scoreAsProgressSetting && (null === $score || empty($score) || -1 == $score)) {
4227
                if ($debug) {
4228
                    error_log("Return false: Dont save score: $score");
4229
                    error_log("progress: $progress");
4230
                }
4231
4232
                return false;
4233
            }
4234
4235
            if ($scoreAsProgress && $scoreAsProgressSetting) {
4236
                $storedProgress = self::getProgress(
4237
                    $this->get_id(),
4238
                    $userId,
4239
                    $course_id,
4240
                    $this->get_lp_session_id()
4241
                );
4242
4243
                // Check if the stored progress is higher than the new value
4244
                if ($storedProgress >= $progress) {
4245
                    if ($debug) {
4246
                        error_log("Return false: New progress value is lower than stored value - Current value: $storedProgress - New value: $progress [lp ".$this->get_id()." - user ".$userId."]");
4247
                    }
4248
4249
                    return false;
4250
                }
4251
            }
4252
            if ($progress >= 0 && $progress <= 100) {
4253
                $progress = (int) $progress;
4254
                $sql = "UPDATE $table SET
4255
                            progress = $progress
4256
                        WHERE
4257
                            c_id = $course_id AND
4258
                            lp_id = ".$this->get_id()." AND
4259
                            user_id = ".$userId." ".$session_condition;
4260
                // Ignore errors as some tables might not have the progress field just yet.
4261
                Database::query($sql);
4262
                $this->progress_db = $progress;
4263
            }
4264
        }
4265
    }
4266
4267
    /**
4268
     * Sets the current item ID (checks if valid and authorized first).
4269
     *
4270
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
4271
     */
4272
    public function set_current_item($item_id = null)
4273
    {
4274
        $debug = $this->debug;
4275
        if ($debug) {
4276
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
4277
        }
4278
        if (empty($item_id)) {
4279
            if ($debug) {
4280
                error_log('No new current item given, ignore...', 0);
4281
            }
4282
            // Do nothing.
4283
        } else {
4284
            if ($debug) {
4285
                error_log('New current item given is '.$item_id.'...', 0);
4286
            }
4287
            if (is_numeric($item_id)) {
4288
                $item_id = (int) $item_id;
4289
                // TODO: Check in database here.
4290
                $this->last = $this->current;
4291
                $this->current = $item_id;
4292
                // TODO: Update $this->index as well.
4293
                foreach ($this->ordered_items as $index => $item) {
4294
                    if ($item == $this->current) {
4295
                        $this->index = $index;
4296
                        break;
4297
                    }
4298
                }
4299
                if ($debug) {
4300
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
4301
                }
4302
            } else {
4303
                if ($debug) {
4304
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
4305
                }
4306
            }
4307
        }
4308
    }
4309
4310
    /**
4311
     * Sets the JS lib setting in the database directly.
4312
     * This is the JavaScript library file this lp needs to load on startup.
4313
     *
4314
     * @param string $lib Proximity setting
4315
     *
4316
     * @return bool True on update success. False otherwise.
4317
     */
4318
    public function set_jslib($lib = '')
4319
    {
4320
        $lp = $this->get_id();
4321
4322
        if (0 != $lp) {
4323
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4324
            $lib = Database::escape_string($lib);
4325
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
4326
                    WHERE iid = $lp";
4327
            $res = Database::query($sql);
4328
4329
            return $res;
4330
        }
4331
4332
        return false;
4333
    }
4334
4335
    /**
4336
     * Set index specified prefix terms for all items in this path.
4337
     *
4338
     * @param string $terms_string Comma-separated list of terms
4339
     * @param string $prefix       Xapian term prefix
4340
     *
4341
     * @return bool False on error, true otherwise
4342
     */
4343
    public function set_terms_by_prefix($terms_string, $prefix)
4344
    {
4345
        $course_id = api_get_course_int_id();
4346
        if ('true' !== api_get_setting('search_enabled')) {
4347
            return false;
4348
        }
4349
4350
        if (!extension_loaded('xapian')) {
4351
            return false;
4352
        }
4353
4354
        $terms_string = trim($terms_string);
4355
        $terms = explode(',', $terms_string);
4356
        array_walk($terms, 'trim_value');
4357
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
4358
4359
        // Don't do anything if no change, verify only at DB, not the search engine.
4360
        if ((0 == count(array_diff($terms, $stored_terms))) && (0 == count(array_diff($stored_terms, $terms)))) {
4361
            return false;
4362
        }
4363
4364
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
4365
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
4366
4367
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
4368
        // TODO: Make query secure agains XSS : use member attr instead of post var.
4369
        $lp_id = (int) $_POST['lp_id'];
4370
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
4371
        $result = Database::query($sql);
4372
        $di = new ChamiloIndexer();
4373
4374
        while ($lp_item = Database::fetch_array($result)) {
4375
            // Get search_did.
4376
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
4377
            $sql = 'SELECT * FROM %s
4378
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
4379
                    LIMIT 1';
4380
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
4381
4382
            //echo $sql; echo '<br>';
4383
            $res = Database::query($sql);
4384
            if (Database::num_rows($res) > 0) {
4385
                $se_ref = Database::fetch_array($res);
4386
                // Compare terms.
4387
                $doc = $di->get_document($se_ref['search_did']);
4388
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
4389
                $xterms = [];
4390
                foreach ($xapian_terms as $xapian_term) {
4391
                    $xterms[] = substr($xapian_term['name'], 1);
4392
                }
4393
4394
                $dterms = $terms;
4395
                $missing_terms = array_diff($dterms, $xterms);
4396
                $deprecated_terms = array_diff($xterms, $dterms);
4397
4398
                // Save it to search engine.
4399
                foreach ($missing_terms as $term) {
4400
                    $doc->add_term($prefix.$term, 1);
4401
                }
4402
                foreach ($deprecated_terms as $term) {
4403
                    $doc->remove_term($prefix.$term);
4404
                }
4405
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
4406
                $di->getDb()->flush();
4407
            }
4408
        }
4409
4410
        return true;
4411
    }
4412
4413
    /**
4414
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
4415
     *
4416
     * @param int $id DB ID of the item
4417
     */
4418
    public function set_previous_item($id)
4419
    {
4420
        if ($this->debug > 0) {
4421
            error_log('In learnpath::set_previous_item()', 0);
4422
        }
4423
        $this->last = $id;
4424
    }
4425
4426
    /**
4427
     * Sets use_max_score.
4428
     *
4429
     * @param int $use_max_score Optional string giving the new location of this learnpath
4430
     *
4431
     * @return bool True on success / False on error
4432
     */
4433
    public function set_use_max_score($use_max_score = 1)
4434
    {
4435
        $use_max_score = (int) $use_max_score;
4436
        $this->use_max_score = $use_max_score;
4437
        $table = Database::get_course_table(TABLE_LP_MAIN);
4438
        $lp_id = $this->get_id();
4439
        $sql = "UPDATE $table SET
4440
                    use_max_score = '".$this->use_max_score."'
4441
                WHERE iid = $lp_id";
4442
        Database::query($sql);
4443
4444
        return true;
4445
    }
4446
4447
    /**
4448
     * Sets and saves the expired_on date.
4449
     *
4450
     * @return bool Returns true if author's name is not empty
4451
     */
4452
    public function set_modified_on()
4453
    {
4454
        $this->modified_on = api_get_utc_datetime();
4455
        $table = Database::get_course_table(TABLE_LP_MAIN);
4456
        $lp_id = $this->get_id();
4457
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
4458
                WHERE iid = $lp_id";
4459
        Database::query($sql);
4460
4461
        return true;
4462
    }
4463
4464
    /**
4465
     * Sets the object's error message.
4466
     *
4467
     * @param string $error Error message. If empty, reinits the error string
4468
     */
4469
    public function set_error_msg($error = '')
4470
    {
4471
        if ($this->debug > 0) {
4472
            error_log('In learnpath::set_error_msg()', 0);
4473
        }
4474
        if (empty($error)) {
4475
            $this->error = '';
4476
        } else {
4477
            $this->error .= $error;
4478
        }
4479
    }
4480
4481
    /**
4482
     * Launches the current item if not 'sco'
4483
     * (starts timer and make sure there is a record ready in the DB).
4484
     *
4485
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
4486
     *
4487
     * @return bool
4488
     */
4489
    public function start_current_item($allow_new_attempt = false)
4490
    {
4491
        $debug = $this->debug;
4492
        if ($debug) {
4493
            error_log('In learnpath::start_current_item()');
4494
            error_log('current: '.$this->current);
4495
        }
4496
        if (0 != $this->current && isset($this->items[$this->current]) &&
4497
            is_object($this->items[$this->current])
4498
        ) {
4499
            $type = $this->get_type();
4500
            $item_type = $this->items[$this->current]->get_type();
4501
            if ($debug) {
4502
                error_log('item type: '.$item_type);
4503
                error_log('lp type: '.$type);
4504
            }
4505
            if ((2 == $type && 'sco' !== $item_type) ||
4506
                (3 == $type && 'au' !== $item_type) ||
4507
                (1 == $type && TOOL_QUIZ != $item_type && TOOL_HOTPOTATOES != $item_type)
4508
            ) {
4509
                $this->items[$this->current]->open($allow_new_attempt);
4510
                $this->autocomplete_parents($this->current);
4511
                $prereq_check = $this->prerequisites_match($this->current);
4512
                if ($debug) {
4513
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
4514
                }
4515
                $this->items[$this->current]->save(false, $prereq_check);
4516
            }
4517
            // If sco, then it is supposed to have been updated by some other call.
4518
            if ('sco' === $item_type) {
4519
                $this->items[$this->current]->restart();
4520
            }
4521
        }
4522
        if ($debug) {
4523
            error_log('lp_view_session_id');
4524
            error_log($this->lp_view_session_id);
4525
            error_log('api session id');
4526
            error_log(api_get_session_id());
4527
            error_log('End of learnpath::start_current_item()');
4528
        }
4529
4530
        return true;
4531
    }
4532
4533
    /**
4534
     * Stops the processing and counters for the old item (as held in $this->last).
4535
     *
4536
     * @return bool True/False
4537
     */
4538
    public function stop_previous_item()
4539
    {
4540
        $debug = $this->debug;
4541
        if ($debug) {
4542
            error_log('In learnpath::stop_previous_item()', 0);
4543
        }
4544
4545
        if (0 != $this->last && $this->last != $this->current &&
4546
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
4547
        ) {
4548
            if ($debug) {
4549
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
4550
            }
4551
            switch ($this->get_type()) {
4552
                case '3':
4553
                    if ('au' != $this->items[$this->last]->get_type()) {
4554
                        if ($debug) {
4555
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
4556
                        }
4557
                        $this->items[$this->last]->close();
4558
                    } else {
4559
                        if ($debug) {
4560
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
4561
                        }
4562
                    }
4563
                    break;
4564
                case '2':
4565
                    if ('sco' != $this->items[$this->last]->get_type()) {
4566
                        if ($debug) {
4567
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
4568
                        }
4569
                        $this->items[$this->last]->close();
4570
                    } else {
4571
                        if ($debug) {
4572
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
4573
                        }
4574
                    }
4575
                    break;
4576
                case '1':
4577
                default:
4578
                    if ($debug) {
4579
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
4580
                    }
4581
                    $this->items[$this->last]->close();
4582
                    break;
4583
            }
4584
        } else {
4585
            if ($debug) {
4586
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
4587
            }
4588
4589
            return false;
4590
        }
4591
4592
        return true;
4593
    }
4594
4595
    /**
4596
     * Updates the default view mode from fullscreen to embedded and inversely.
4597
     *
4598
     * @return string The current default view mode ('fullscreen' or 'embedded')
4599
     */
4600
    public function update_default_view_mode()
4601
    {
4602
        $table = Database::get_course_table(TABLE_LP_MAIN);
4603
        $sql = "SELECT * FROM $table
4604
                WHERE iid = ".$this->get_id();
4605
        $res = Database::query($sql);
4606
        if (Database::num_rows($res) > 0) {
4607
            $row = Database::fetch_array($res);
4608
            $default_view_mode = $row['default_view_mod'];
4609
            $view_mode = $default_view_mode;
4610
            switch ($default_view_mode) {
4611
                case 'fullscreen': // default with popup
4612
                    $view_mode = 'embedded';
4613
                    break;
4614
                case 'embedded': // default view with left menu
4615
                    $view_mode = 'embedframe';
4616
                    break;
4617
                case 'embedframe': //folded menu
4618
                    $view_mode = 'impress';
4619
                    break;
4620
                case 'impress':
4621
                    $view_mode = 'fullscreen';
4622
                    break;
4623
            }
4624
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
4625
                    WHERE iid = ".$this->get_id();
4626
            Database::query($sql);
4627
            $this->mode = $view_mode;
4628
4629
            return $view_mode;
4630
        }
4631
4632
        return -1;
4633
    }
4634
4635
    /**
4636
     * Updates the default behaviour about auto-commiting SCORM updates.
4637
     *
4638
     * @return bool True if auto-commit has been set to 'on', false otherwise
4639
     */
4640
    public function update_default_scorm_commit()
4641
    {
4642
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4643
        $sql = "SELECT * FROM $lp_table
4644
                WHERE iid = ".$this->get_id();
4645
        $res = Database::query($sql);
4646
        if (Database::num_rows($res) > 0) {
4647
            $row = Database::fetch_array($res);
4648
            $force = $row['force_commit'];
4649
            if (1 == $force) {
4650
                $force = 0;
4651
                $force_return = false;
4652
            } elseif (0 == $force) {
4653
                $force = 1;
4654
                $force_return = true;
4655
            }
4656
            $sql = "UPDATE $lp_table SET force_commit = $force
4657
                    WHERE iid = ".$this->get_id();
4658
            Database::query($sql);
4659
            $this->force_commit = $force_return;
4660
4661
            return $force_return;
4662
        }
4663
4664
        return -1;
4665
    }
4666
4667
    /**
4668
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
4669
     *
4670
     * @return bool True on success, false on failure
4671
     */
4672
    public function update_display_order()
4673
    {
4674
        return;
4675
        $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...
4676
        $table = Database::get_course_table(TABLE_LP_MAIN);
4677
        $sql = "SELECT * FROM $table
4678
                WHERE c_id = $course_id
4679
                ORDER BY display_order";
4680
        $res = Database::query($sql);
4681
        if (false === $res) {
4682
            return false;
4683
        }
4684
4685
        $num = Database::num_rows($res);
4686
        // First check the order is correct, globally (might be wrong because
4687
        // of versions < 1.8.4).
4688
        if ($num > 0) {
4689
            $i = 1;
4690
            while ($row = Database::fetch_array($res)) {
4691
                if ($row['display_order'] != $i) {
4692
                    // If we find a gap in the order, we need to fix it.
4693
                    $sql = "UPDATE $table SET display_order = $i
4694
                            WHERE iid = ".$row['iid'];
4695
                    Database::query($sql);
4696
                }
4697
                $i++;
4698
            }
4699
        }
4700
4701
        return true;
4702
    }
4703
4704
    /**
4705
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
4706
     *
4707
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
4708
     */
4709
    public function update_reinit()
4710
    {
4711
        $force = $this->entity->getPreventReinit();
4712
        if (1 == $force) {
4713
            $force = 0;
4714
        } elseif (0 == $force) {
4715
            $force = 1;
4716
        }
4717
4718
        $table = Database::get_course_table(TABLE_LP_MAIN);
4719
        $sql = "UPDATE $table SET prevent_reinit = $force
4720
                WHERE iid = ".$this->get_id();
4721
        Database::query($sql);
4722
        $this->prevent_reinit = $force;
4723
4724
        return $force;
4725
    }
4726
4727
    /**
4728
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
4729
     *
4730
     * @return string 'single', 'multi' or 'seriousgame'
4731
     *
4732
     * @author ndiechburg <[email protected]>
4733
     */
4734
    public function get_attempt_mode()
4735
    {
4736
        //Set default value for seriousgame_mode
4737
        if (!isset($this->seriousgame_mode)) {
4738
            $this->seriousgame_mode = 0;
4739
        }
4740
        // Set default value for prevent_reinit
4741
        if (!isset($this->prevent_reinit)) {
4742
            $this->prevent_reinit = 1;
4743
        }
4744
        if (1 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
4745
            return 'seriousgame';
4746
        }
4747
        if (0 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
4748
            return 'single';
4749
        }
4750
        if (0 == $this->seriousgame_mode && 0 == $this->prevent_reinit) {
4751
            return 'multiple';
4752
        }
4753
4754
        return 'single';
4755
    }
4756
4757
    /**
4758
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
4759
     *
4760
     * @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...
4761
     *
4762
     * @return bool
4763
     *
4764
     * @author ndiechburg <[email protected]>
4765
     */
4766
    public function set_attempt_mode($mode)
4767
    {
4768
        switch ($mode) {
4769
            case 'seriousgame':
4770
                $sg_mode = 1;
4771
                $prevent_reinit = 1;
4772
                break;
4773
            case 'single':
4774
                $sg_mode = 0;
4775
                $prevent_reinit = 1;
4776
                break;
4777
            case 'multiple':
4778
                $sg_mode = 0;
4779
                $prevent_reinit = 0;
4780
                break;
4781
            default:
4782
                $sg_mode = 0;
4783
                $prevent_reinit = 0;
4784
                break;
4785
        }
4786
        $this->prevent_reinit = $prevent_reinit;
4787
        $this->seriousgame_mode = $sg_mode;
4788
        $table = Database::get_course_table(TABLE_LP_MAIN);
4789
        $sql = "UPDATE $table SET
4790
                prevent_reinit = $prevent_reinit ,
4791
                seriousgame_mode = $sg_mode
4792
                WHERE iid = ".$this->get_id();
4793
        $res = Database::query($sql);
4794
        if ($res) {
0 ignored issues
show
introduced by
$res is of type Doctrine\DBAL\Driver\Statement, thus it always evaluated to true.
Loading history...
4795
            return true;
4796
        } else {
4797
            return false;
4798
        }
4799
    }
4800
4801
    /**
4802
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
4803
     *
4804
     * @author ndiechburg <[email protected]>
4805
     */
4806
    public function switch_attempt_mode()
4807
    {
4808
        $mode = $this->get_attempt_mode();
4809
        switch ($mode) {
4810
            case 'single':
4811
                $next_mode = 'multiple';
4812
                break;
4813
            case 'multiple':
4814
                $next_mode = 'seriousgame';
4815
                break;
4816
            case 'seriousgame':
4817
            default:
4818
                $next_mode = 'single';
4819
                break;
4820
        }
4821
        $this->set_attempt_mode($next_mode);
4822
    }
4823
4824
    /**
4825
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
4826
     * but possibility to do again a completed item.
4827
     *
4828
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
4829
     *
4830
     * @author ndiechburg <[email protected]>
4831
     */
4832
    public function set_seriousgame_mode()
4833
    {
4834
        $table = Database::get_course_table(TABLE_LP_MAIN);
4835
        $force = $this->entity->getSeriousgameMode();
4836
        if (1 == $force) {
4837
            $force = 0;
4838
        } elseif (0 == $force) {
4839
            $force = 1;
4840
        }
4841
        $sql = "UPDATE $table SET seriousgame_mode = $force
4842
                WHERE iid = ".$this->get_id();
4843
        Database::query($sql);
4844
        $this->seriousgame_mode = $force;
4845
4846
        return $force;
4847
    }
4848
4849
    /**
4850
     * Updates the "scorm_debug" value that shows or hide the debug window.
4851
     *
4852
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
4853
     */
4854
    public function update_scorm_debug()
4855
    {
4856
        $table = Database::get_course_table(TABLE_LP_MAIN);
4857
        $force = $this->entity->getDebug();
4858
        if (1 == $force) {
4859
            $force = 0;
4860
        } elseif (0 == $force) {
4861
            $force = 1;
4862
        }
4863
        $sql = "UPDATE $table SET debug = $force
4864
                WHERE iid = ".$this->get_id();
4865
        Database::query($sql);
4866
        $this->scorm_debug = $force;
4867
4868
        return $force;
4869
    }
4870
4871
    /**
4872
     * Function that makes a call to the function sort_tree_array and create_tree_array.
4873
     *
4874
     * @author Kevin Van Den Haute
4875
     *
4876
     * @param  array
4877
     */
4878
    public function tree_array($array)
4879
    {
4880
        $array = $this->sort_tree_array($array);
4881
        $this->create_tree_array($array);
4882
    }
4883
4884
    /**
4885
     * Creates an array with the elements of the learning path tree in it.
4886
     *
4887
     * @author Kevin Van Den Haute
4888
     *
4889
     * @param array $array
4890
     * @param int   $parent
4891
     * @param int   $depth
4892
     * @param array $tmp
4893
     */
4894
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
4895
    {
4896
        if (is_array($array)) {
4897
            for ($i = 0; $i < count($array); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

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

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

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
4898
                if ($array[$i]['parent_item_id'] == $parent) {
4899
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
4900
                        $tmp[] = $array[$i]['parent_item_id'];
4901
                        $depth++;
4902
                    }
4903
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
4904
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
4905
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
4906
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
4907
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
4908
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
4909
                    $this->arrMenu[] = [
4910
                        'id' => $array[$i]['id'],
4911
                        'ref' => $ref,
4912
                        'item_type' => $array[$i]['item_type'],
4913
                        'title' => $array[$i]['title'],
4914
                        'title_raw' => $array[$i]['title_raw'],
4915
                        'path' => $path,
4916
                        'description' => $array[$i]['description'],
4917
                        'parent_item_id' => $array[$i]['parent_item_id'],
4918
                        'previous_item_id' => $array[$i]['previous_item_id'],
4919
                        'next_item_id' => $array[$i]['next_item_id'],
4920
                        'min_score' => $array[$i]['min_score'],
4921
                        'max_score' => $array[$i]['max_score'],
4922
                        'mastery_score' => $array[$i]['mastery_score'],
4923
                        'display_order' => $array[$i]['display_order'],
4924
                        'prerequisite' => $preq,
4925
                        'depth' => $depth,
4926
                        'audio' => $audio,
4927
                        'prerequisite_min_score' => $prerequisiteMinScore,
4928
                        'prerequisite_max_score' => $prerequisiteMaxScore,
4929
                    ];
4930
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
4931
                }
4932
            }
4933
        }
4934
    }
4935
4936
    /**
4937
     * Sorts a multi dimensional array by parent id and display order.
4938
     *
4939
     * @author Kevin Van Den Haute
4940
     *
4941
     * @param array $array (array with al the learning path items in it)
4942
     *
4943
     * @return array
4944
     */
4945
    public function sort_tree_array($array)
4946
    {
4947
        foreach ($array as $key => $row) {
4948
            $parent[$key] = $row['parent_item_id'];
4949
            $position[$key] = $row['display_order'];
4950
        }
4951
4952
        if (count($array) > 0) {
4953
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
4954
        }
4955
4956
        return $array;
4957
    }
4958
4959
    /**
4960
     * Function that creates a html list of learning path items so that we can add audio files to them.
4961
     *
4962
     * @author Kevin Van Den Haute
4963
     *
4964
     * @return string
4965
     */
4966
    public function overview()
4967
    {
4968
        $return = '';
4969
        $update_audio = $_GET['updateaudio'] ?? null;
4970
4971
        // we need to start a form when we want to update all the mp3 files
4972
        if ('true' == $update_audio) {
4973
            $return .= '<form action="'.api_get_self().'?'.api_get_cidreq().'&updateaudio='.Security::remove_XSS(
4974
                    $_GET['updateaudio']
4975
                ).'&action='.Security::remove_XSS(
4976
                    $_GET['action']
4977
                ).'&lp_id='.$_SESSION['oLP']->lp_id.'" method="post" enctype="multipart/form-data" name="updatemp3" id="updatemp3">';
4978
        }
4979
        $return .= '<div id="message"></div>';
4980
        if (0 == count($this->items)) {
4981
            $return .= Display::return_message(
4982
                get_lang(
4983
                    'You should add some items to your learning path, otherwise you won\'t be able to attach audio files to them'
4984
                ),
4985
                'normal'
4986
            );
4987
        } else {
4988
            $return_audio = '<table class="table table-hover table-striped data_table">';
4989
            $return_audio .= '<tr>';
4990
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
4991
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
4992
            $return_audio .= '</tr>';
4993
4994
            if ('true' != $update_audio) {
4995
                /*$return .= '<div class="col-md-12">';
4996
                $return .= self::return_new_tree($update_audio);
4997
                $return .= '</div>';*/
4998
                $return .= Display::div(
4999
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
5000
                    ['style' => 'float:left; margin-top:15px;width:100%']
5001
                );
5002
            } else {
5003
                //$return_audio .= self::return_new_tree($update_audio);
5004
                $return .= $return_audio.'</table>';
5005
            }
5006
5007
            // We need to close the form when we are updating the mp3 files.
5008
            if ('true' == $update_audio) {
5009
                $return .= '<div class="footer-audio">';
5010
                $return .= Display::button(
5011
                    'save_audio',
5012
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('Save audio and organization'),
5013
                    ['class' => 'btn btn-primary', 'type' => 'submit']
5014
                );
5015
                $return .= '</div>';
5016
            }
5017
        }
5018
5019
        // We need to close the form when we are updating the mp3 files.
5020
        if ('true' === $update_audio && isset($this->arrMenu) && 0 != count($this->arrMenu)) {
5021
            $return .= '</form>';
5022
        }
5023
5024
        return $return;
5025
    }
5026
5027
    /**
5028
     * @param string $update_audio
5029
     *
5030
     * @return array
5031
     */
5032
    public function processBuildMenuElements($update_audio = 'false')
5033
    {
5034
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
5035
        $arrLP = $this->getItemsForForm();
5036
5037
        $this->tree_array($arrLP);
5038
        $arrLP = $this->arrMenu ?? [];
5039
        $default_data = null;
5040
        $default_content = null;
5041
        $elements = [];
5042
        $return_audio = null;
5043
        $iconPath = api_get_path(SYS_PUBLIC_PATH).'img/';
5044
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
5045
        $countItems = count($arrLP);
5046
5047
        $upIcon = Display::return_icon(
5048
            'up.png',
5049
            get_lang('Up'),
5050
            [],
5051
            ICON_SIZE_TINY
5052
        );
5053
5054
        $disableUpIcon = Display::return_icon(
5055
            'up_na.png',
5056
            get_lang('Up'),
5057
            [],
5058
            ICON_SIZE_TINY
5059
        );
5060
5061
        $downIcon = Display::return_icon(
5062
            'down.png',
5063
            get_lang('Down'),
5064
            [],
5065
            ICON_SIZE_TINY
5066
        );
5067
5068
        $disableDownIcon = Display::return_icon(
5069
            'down_na.png',
5070
            get_lang('Down'),
5071
            [],
5072
            ICON_SIZE_TINY
5073
        );
5074
5075
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
5076
5077
        $pluginCalendar = 'true' === api_get_plugin_setting('learning_calendar', 'enabled');
5078
        $plugin = null;
5079
        if ($pluginCalendar) {
5080
            $plugin = LearningCalendarPlugin::create();
5081
        }
5082
5083
        for ($i = 0; $i < $countItems; $i++) {
5084
            $parent_id = $arrLP[$i]['parent_item_id'];
5085
            $title = $arrLP[$i]['title'];
5086
            $title_cut = $arrLP[$i]['title_raw'];
5087
            if (false === $show) {
5088
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
5089
            }
5090
            // Link for the documents
5091
            if ('document' === $arrLP[$i]['item_type'] || TOOL_READOUT_TEXT === $arrLP[$i]['item_type']) {
5092
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5093
                $title_cut = Display::url(
5094
                    $title_cut,
5095
                    $url,
5096
                    [
5097
                        'class' => 'ajax moved',
5098
                        'data-title' => $title,
5099
                        'title' => $title,
5100
                    ]
5101
                );
5102
            }
5103
5104
            // Detect if type is FINAL_ITEM to set path_id to SESSION
5105
            if (TOOL_LP_FINAL_ITEM === $arrLP[$i]['item_type']) {
5106
                Session::write('pathItem', $arrLP[$i]['path']);
5107
            }
5108
5109
            $oddClass = 'row_even';
5110
            if (0 == ($i % 2)) {
5111
                $oddClass = 'row_odd';
5112
            }
5113
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
5114
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
5115
5116
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
5117
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
5118
            } else {
5119
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
5120
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
5121
                } else {
5122
                    if (TOOL_LP_FINAL_ITEM === $arrLP[$i]['item_type']) {
5123
                        $icon = Display::return_icon('certificate.png');
5124
                    } else {
5125
                        $icon = Display::return_icon('folder_document.png');
5126
                    }
5127
                }
5128
            }
5129
5130
            // The audio column.
5131
            $return_audio .= '<td align="left" style="padding-left:10px;">';
5132
            $audio = '';
5133
            if (!$update_audio || 'true' != $update_audio) {
5134
                if (empty($arrLP[$i]['audio'])) {
5135
                    $audio .= '';
5136
                }
5137
            } else {
5138
                $types = self::getChapterTypes();
5139
                if (!in_array($arrLP[$i]['item_type'], $types)) {
5140
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
5141
                    if (!empty($arrLP[$i]['audio'])) {
5142
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
5143
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('Remove audio');
5144
                    }
5145
                }
5146
            }
5147
5148
            $return_audio .= Display::span($icon.' '.$title).
5149
                Display::tag(
5150
                    'td',
5151
                    $audio,
5152
                    ['style' => '']
5153
                );
5154
            $return_audio .= '</td>';
5155
            $move_icon = '';
5156
            $move_item_icon = '';
5157
            $edit_icon = '';
5158
            $delete_icon = '';
5159
            $audio_icon = '';
5160
            $prerequisities_icon = '';
5161
            $forumIcon = '';
5162
            $previewIcon = '';
5163
            $pluginCalendarIcon = '';
5164
            $orderIcons = '';
5165
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
5166
5167
            if ($is_allowed_to_edit) {
5168
                if (!$update_audio || 'true' != $update_audio) {
5169
                    if (TOOL_LP_FINAL_ITEM !== $arrLP[$i]['item_type']) {
5170
                        $move_icon .= '<a class="moved" href="#">';
5171
                        $move_icon .= Display::return_icon(
5172
                            'move_everywhere.png',
5173
                            get_lang('Move'),
5174
                            [],
5175
                            ICON_SIZE_TINY
5176
                        );
5177
                        $move_icon .= '</a>';
5178
                    }
5179
                }
5180
5181
                // No edit for this item types
5182
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
5183
                    if ('dir' != $arrLP[$i]['item_type']) {
5184
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'&path_item='.$arrLP[$i]['path'].'" class="btn btn-default">';
5185
                        $edit_icon .= Display::return_icon(
5186
                            'edit.png',
5187
                            get_lang('Edit section description/name'),
5188
                            [],
5189
                            ICON_SIZE_TINY
5190
                        );
5191
                        $edit_icon .= '</a>';
5192
5193
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
5194
                            $forumThread = null;
5195
                            if (isset($this->items[$arrLP[$i]['id']])) {
5196
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
5197
                                    $this->course_int_id,
5198
                                    $this->lp_session_id
5199
                                );
5200
                            }
5201
                            if ($forumThread) {
5202
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
5203
                                        'action' => 'dissociate_forum',
5204
                                        'id' => $arrLP[$i]['id'],
5205
                                        'lp_id' => $this->lp_id,
5206
                                    ]);
5207
                                $forumIcon = Display::url(
5208
                                    Display::return_icon(
5209
                                        'forum.png',
5210
                                        get_lang('Dissociate the forum of this learning path item'),
5211
                                        [],
5212
                                        ICON_SIZE_TINY
5213
                                    ),
5214
                                    $forumIconUrl,
5215
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
5216
                                );
5217
                            } else {
5218
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
5219
                                        'action' => 'create_forum',
5220
                                        'id' => $arrLP[$i]['id'],
5221
                                        'lp_id' => $this->lp_id,
5222
                                    ]);
5223
                                $forumIcon = Display::url(
5224
                                    Display::return_icon(
5225
                                        'forum.png',
5226
                                        get_lang('Associate a forum to this learning path item'),
5227
                                        [],
5228
                                        ICON_SIZE_TINY
5229
                                    ),
5230
                                    $forumIconUrl,
5231
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
5232
                                );
5233
                            }
5234
                        }
5235
                    } else {
5236
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'&path_item='.$arrLP[$i]['path'].'" class="btn btn-default">';
5237
                        $edit_icon .= Display::return_icon(
5238
                            'edit.png',
5239
                            get_lang('Edit section description/name'),
5240
                            [],
5241
                            ICON_SIZE_TINY
5242
                        );
5243
                        $edit_icon .= '</a>';
5244
                    }
5245
                } else {
5246
                    if (TOOL_LP_FINAL_ITEM == $arrLP[$i]['item_type']) {
5247
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
5248
                        $edit_icon .= Display::return_icon(
5249
                            'edit.png',
5250
                            get_lang('Edit'),
5251
                            [],
5252
                            ICON_SIZE_TINY
5253
                        );
5254
                        $edit_icon .= '</a>';
5255
                    }
5256
                }
5257
5258
                if ($pluginCalendar) {
5259
                    $pluginLink = $pluginUrl.
5260
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5261
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('1 day'), [], ICON_SIZE_TINY);
5262
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
5263
                    if ($itemInfo && 1 == $itemInfo['value']) {
5264
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('1 day'), [], ICON_SIZE_TINY);
5265
                    }
5266
                    $pluginCalendarIcon = Display::url(
5267
                        $iconCalendar,
5268
                        $pluginLink,
5269
                        ['class' => 'btn btn-default']
5270
                    );
5271
                }
5272
5273
                if ('final_item' != $arrLP[$i]['item_type']) {
5274
                    $orderIcons = Display::url(
5275
                        $upIcon,
5276
                        'javascript:void(0)',
5277
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
5278
                    );
5279
                    $orderIcons .= Display::url(
5280
                        $downIcon,
5281
                        'javascript:void(0)',
5282
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
5283
                    );
5284
                }
5285
                $delete_icon .= ' <a
5286
5287
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
5288
                    onclick="return confirmation(\''.addslashes($title).'\');"
5289
                    class="btn btn-default">';
5290
                $delete_icon .= Display::return_icon(
5291
                    'delete.png',
5292
                    get_lang('Delete section'),
5293
                    [],
5294
                    ICON_SIZE_TINY
5295
                );
5296
                $delete_icon .= '</a>';
5297
5298
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5299
                $previewImage = Display::return_icon(
5300
                    'preview_view.png',
5301
                    get_lang('Preview'),
5302
                    [],
5303
                    ICON_SIZE_TINY
5304
                );
5305
5306
                switch ($arrLP[$i]['item_type']) {
5307
                    case TOOL_DOCUMENT:
5308
                    case TOOL_LP_FINAL_ITEM:
5309
                    case TOOL_READOUT_TEXT:
5310
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5311
                        $previewIcon = Display::url(
5312
                            $previewImage,
5313
                            $urlPreviewLink,
5314
                            [
5315
                                'target' => '_blank',
5316
                                'class' => 'btn btn-default',
5317
                                'data-title' => $arrLP[$i]['title'],
5318
                                'title' => $arrLP[$i]['title'],
5319
                            ]
5320
                        );
5321
                        break;
5322
                    case TOOL_THREAD:
5323
                    case TOOL_FORUM:
5324
                    case TOOL_QUIZ:
5325
                    case TOOL_STUDENTPUBLICATION:
5326
                    case TOOL_LINK:
5327
                        $class = 'btn btn-default';
5328
                        $target = '_blank';
5329
                        $link = self::rl_get_resource_link_for_learnpath(
5330
                            $this->course_int_id,
5331
                            $this->lp_id,
5332
                            $arrLP[$i]['id'],
5333
                            0
5334
                        );
5335
                        $previewIcon = Display::url(
5336
                            $previewImage,
5337
                            $link,
5338
                            [
5339
                                'class' => $class,
5340
                                'data-title' => $arrLP[$i]['title'],
5341
                                'title' => $arrLP[$i]['title'],
5342
                                'target' => $target,
5343
                            ]
5344
                        );
5345
                        break;
5346
                    default:
5347
                        $previewIcon = Display::url(
5348
                            $previewImage,
5349
                            $url.'&action=view_item',
5350
                            ['class' => 'btn btn-default', 'target' => '_blank']
5351
                        );
5352
                        break;
5353
                }
5354
5355
                if ('dir' != $arrLP[$i]['item_type']) {
5356
                    $prerequisities_icon = Display::url(
5357
                        Display::return_icon(
5358
                            'accept.png',
5359
                            get_lang('Prerequisites'),
5360
                            [],
5361
                            ICON_SIZE_TINY
5362
                        ),
5363
                        $url.'&action=edit_item_prereq',
5364
                        ['class' => 'btn btn-default']
5365
                    );
5366
                    if ('final_item' != $arrLP[$i]['item_type']) {
5367
                        /*$move_item_icon = Display::url(
5368
                            Display::return_icon(
5369
                                'move.png',
5370
                                get_lang('Move'),
5371
                                [],
5372
                                ICON_SIZE_TINY
5373
                            ),
5374
                            $url.'&action=move_item',
5375
                            ['class' => 'btn btn-default']
5376
                        );*/
5377
                    }
5378
                    $audio_icon = Display::url(
5379
                        Display::return_icon(
5380
                            'audio.png',
5381
                            get_lang('Upload'),
5382
                            [],
5383
                            ICON_SIZE_TINY
5384
                        ),
5385
                        $url.'&action=add_audio',
5386
                        ['class' => 'btn btn-default']
5387
                    );
5388
                }
5389
            }
5390
            if ('true' != $update_audio) {
5391
                $row = $move_icon.' '.$icon.
5392
                    Display::span($title_cut).
5393
                    Display::tag(
5394
                        'div',
5395
                        "<div class=\"btn-group btn-group-xs\">
5396
                                    $previewIcon
5397
                                    $audio
5398
                                    $edit_icon
5399
                                    $pluginCalendarIcon
5400
                                    $forumIcon
5401
                                    $prerequisities_icon
5402
                                    $move_item_icon
5403
                                    $audio_icon
5404
                                    $orderIcons
5405
                                    $delete_icon
5406
                                </div>",
5407
                        ['class' => 'btn-toolbar button_actions']
5408
                    );
5409
            } else {
5410
                $row =
5411
                    Display::span($title.$icon).
5412
                    Display::span($audio, ['class' => 'button_actions']);
5413
            }
5414
5415
            $default_data[$arrLP[$i]['id']] = $row;
5416
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
5417
5418
            if (empty($parent_id)) {
5419
                $elements[$arrLP[$i]['id']]['data'] = $row;
5420
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
5421
            } else {
5422
                $parent_arrays = [];
5423
                if ($arrLP[$i]['depth'] > 1) {
5424
                    // Getting list of parents
5425
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
5426
                        foreach ($arrLP as $item) {
5427
                            if ($item['id'] == $parent_id) {
5428
                                if (0 == $item['parent_item_id']) {
5429
                                    $parent_id = $item['id'];
5430
                                    break;
5431
                                } else {
5432
                                    $parent_id = $item['parent_item_id'];
5433
                                    if (empty($parent_arrays)) {
5434
                                        $parent_arrays[] = intval($item['id']);
5435
                                    }
5436
                                    $parent_arrays[] = $parent_id;
5437
                                    break;
5438
                                }
5439
                            }
5440
                        }
5441
                    }
5442
                }
5443
5444
                if (!empty($parent_arrays)) {
5445
                    $parent_arrays = array_reverse($parent_arrays);
5446
                    $val = '$elements';
5447
                    $x = 0;
5448
                    foreach ($parent_arrays as $item) {
5449
                        if ($x != count($parent_arrays) - 1) {
5450
                            $val .= '["'.$item.'"]["children"]';
5451
                        } else {
5452
                            $val .= '["'.$item.'"]["children"]';
5453
                        }
5454
                        $x++;
5455
                    }
5456
                    $val .= "";
5457
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
5458
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
5459
                } else {
5460
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
5461
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
5462
                }
5463
            }
5464
        }
5465
5466
        return [
5467
            'elements' => $elements,
5468
            'default_data' => $default_data,
5469
            'default_content' => $default_content,
5470
            'return_audio' => $return_audio,
5471
        ];
5472
    }
5473
5474
    /**
5475
     * @param string $updateAudio true/false strings
5476
     *
5477
     * @return string
5478
     */
5479
    public function returnLpItemList($updateAudio)
5480
    {
5481
        $result = $this->processBuildMenuElements($updateAudio);
5482
5483
        $html = self::print_recursive(
5484
            $result['elements'],
5485
            $result['default_data'],
5486
            $result['default_content']
5487
        );
5488
5489
        if (!empty($html)) {
5490
            $html .= Display::return_message(get_lang('Drag and drop an element here'));
5491
        }
5492
5493
        return $html;
5494
    }
5495
5496
    public function showBuildSideBar($updateAudio = false, $dropElementHere = false, $type = null)
5497
    {
5498
        $sureToDelete = trim(get_lang('Are you sure to delete?'));
5499
        $ajax_url = api_get_path(WEB_AJAX_PATH).'lp.ajax.php?lp_id='.$this->get_id().'&'.api_get_cidreq();
5500
5501
        $content = '
5502
        <script>
5503
            /*
5504
            Script to manipulate Learning Path items with Drag and drop
5505
             */
5506
            var newOrderData = "";
5507
            function buildLPtree(in_elem, in_parent_id) {
5508
                var item_tag = in_elem.get(0).tagName;
5509
                var item_id =  in_elem.attr("id");
5510
                //var parent_id = item_id;
5511
                in_elem.children("li").each(function () {
5512
                    let itemId = $(this).attr("id");
5513
                    if (itemId && itemId != "undefined") {
5514
                        newOrderData += itemId + "|" + in_parent_id+"^";
5515
                        $(this).find("ul").each(function () {
5516
                            buildLPtree($(this), itemId);
5517
                        });
5518
                    }
5519
                });
5520
            }
5521
5522
            $(function() {
5523
                $(".lp_resource6").sortable({
5524
                    items: ".lp_resource_element ",
5525
                    handle: ".moved", //only the class "moved"
5526
                    cursor: "move",
5527
                    connectWith: "#lp_item_list",
5528
                    placeholder: "ui-state-highlight", //defines the yellow highlight
5529
                    start: function(event, ui) {
5530
                        $(ui.item).css("width", "350px");
5531
                        $(ui.item).find(".item_data").attr("style", "");
5532
                    },
5533
                    stop: function(event, ui) {
5534
                        $(ui.item).css("width", "100%");
5535
                    }
5536
                });
5537
5538
                $(".li_container2 .order_items").click(function(e) {
5539
                    var dir = $(this).data("dir");
5540
                    var itemId = $(this).data("id");
5541
                    var jItems = $("#lp_item_list li.li_container");
5542
                    var jItem = $("#"+ itemId);
5543
                    var index = jItems.index(jItem);
5544
                    var total = jItems.length;
5545
5546
                    switch (dir) {
5547
                        case "up":
5548
                            if (index != 0 && jItems[index - 1]) {
5549
                                /*var subItems = $(jItems[index - 1]).find("li.sub_item");
5550
                                if (subItems.length >= 0) {
5551
                                    index = index - 1;
5552
                                }*/
5553
                                var subItems = $(jItems[index - 1]).find("li.sub_item");
5554
                                var parentClass = $(jItems[index - 1]).parent().parent().attr("class");
5555
                                var parentId = $(jItems[index]).parent().parent().attr("id");
5556
                                var myParentId = $(jItems[index - 1]).parent().parent().attr("id");
5557
5558
                                // We are brothers!
5559
                                if (parentId == myParentId) {
5560
                                    if (subItems.length > 0) {
5561
                                        var lastItem = $(jItems[index - 1]).find("li.sub_item");
5562
                                        parentIndex = jItems.index(lastItem);
5563
                                        jItem.detach().insertAfter(lastItem);
5564
                                    } else {
5565
                                        jItem.detach().insertBefore(jItems[index - 1]);
5566
                                    }
5567
                                    break;
5568
                                }
5569
5570
                                if (parentClass == "record li_container") {
5571
                                    // previous is a chapter
5572
                                    var lastItem = $(jItems[index - 1]).parent().parent().find("li.li_container").last();
5573
                                    parentIndex = jItems.index(lastItem);
5574
                                    jItem.detach().insertAfter(jItems[parentIndex]);
5575
                                } else {
5576
                                    jItem.detach().insertBefore(jItems[index - 1]);
5577
                                }
5578
                            }
5579
                            break;
5580
                        case "down":
5581
                             if (index != total - 1) {
5582
                                const originIndex = index;
5583
                                // The element is a chapter with items
5584
                                var subItems = jItem.find("li.li_container");
5585
                                if (subItems.length > 0) {
5586
                                    index = subItems.length + index;
5587
                                }
5588
5589
                                var subItems = $(jItems[index + 1]).find("li.sub_item");
5590
                                // This is an element entering in a chapter
5591
                                if (subItems.length > 0) {
5592
                                    // Check if im a child
5593
                                    var parentClass = jItem.parent().parent().attr("class");
5594
                                    if (parentClass == "record li_container") {
5595
                                        // Parent position
5596
                                        var parentIndex = jItems.index(jItem.parent().parent());
5597
                                        jItem.detach().insertAfter(jItems[parentIndex]);
5598
                                    } else {
5599
                                        jItem.detach().insertAfter(subItems);
5600
                                    }
5601
                                    break;
5602
                                }
5603
5604
                                var currentSubItems = $(jItems[index]).parent().find("li.sub_item");
5605
                                var parentId = $(jItems[originIndex]).parent().parent().attr("id");
5606
                                var myParentId = $(jItems[index + 1]).parent().parent().attr("id");
5607
5608
                                // We are brothers!
5609
                                if (parentId == myParentId) {
5610
                                    if ((index + 1) < total) {
5611
                                        jItem.detach().insertAfter(jItems[index + 1]);
5612
                                    }
5613
                                    break;
5614
                                }
5615
5616
                                if (currentSubItems.length > 0) {
5617
                                    var parentIndex = jItems.index(jItem.parent().parent());
5618
                                    if (parentIndex >= 0) {
5619
                                        jItem.detach().insertAfter(jItems[parentIndex]);
5620
                                        break;
5621
                                    }
5622
                                    //jItem.detach().insertAfter($(jItems[index]).parent().parent());
5623
                                }
5624
5625
                                //var lastItem = $(jItems[index + 1]).parent().parent().find("li.li_container").last();
5626
                                if (subItems.length > 0) {
5627
                                    index = originIndex;
5628
                                }
5629
5630
                                if ((index + 1) < total) {
5631
                                    jItem.detach().insertAfter(jItems[index + 1]);
5632
                                }
5633
                             }
5634
                             break;
5635
                    }
5636
5637
                    buildLPtree($("#lp_item_list"), 0);
5638
                    var order = "new_order="+ newOrderData + "&a=update_lp_item_order";
5639
                    $.get(
5640
                        "'.$ajax_url.'",
5641
                        order,
5642
                        function(reponse) {
5643
                            $("#message").html(reponse);
5644
                            order = "";
5645
                            newOrderData = "";
5646
                        }
5647
                    );
5648
                });
5649
5650
                function refreshTree() {
5651
                    var params = "&a=get_lp_item_tree";
5652
                    $.get(
5653
                        "'.$ajax_url.'",
5654
                        params,
5655
                        function(result) {
5656
                            serialized = [];
5657
                            $("#lp_item_list").html(result);
5658
                        }
5659
                    );
5660
                }
5661
5662
                const nestedQuery = ".nested-sortable";
5663
                const identifier = "id";
5664
                const root = document.getElementById("lp_item_list");
5665
                /*function serialize(sortable) {
5666
                  var serialized = [];
5667
                  var children = [].slice.call(sortable.children);
5668
                  for (var i in children) {
5669
                    var nested = children[i].querySelector(nestedQuery);
5670
                    serialized.push({
5671
                      id: children[i].dataset[identifier],
5672
                      children: nested ? serialize(nested) : []
5673
                    });
5674
                  }
5675
                  return serialized;
5676
                }*/
5677
                var serialized = [];
5678
                function serialize(sortable) {
5679
                  var children = [].slice.call(sortable.children);
5680
                  for (var i in children) {
5681
                    var nested = children[i].querySelector(nestedQuery);
5682
                    var parentId = $(children[i]).parent().parent().attr("id");
5683
                    //console.log("---");
5684
                    var id = children[i].dataset[identifier];
5685
                    if (typeof id === "undefined") {
5686
                        return;
5687
                    }
5688
                    //console.log(id);            console.log(parentId);
5689
                    serialized.push({
5690
                      id: children[i].dataset[identifier],
5691
                      parent_id: parentId
5692
                    });
5693
5694
                    if (nested) {
5695
                        serialize(nested);
5696
                    }
5697
                  }
5698
5699
                  return serialized;
5700
                }
5701
5702
                let tree = document.getElementById("lp_item_list");
5703
                Sortable.create(tree, {
5704
                    group: "nested",
5705
                    put: ["nested-sortable", ".lp_resource", ".nested-source"],
5706
                    animation: 150,
5707
                    //fallbackOnBody: true,
5708
                    swapThreshold: 0.65,
5709
                    dataIdAttr: "data-id",
5710
                    store: {
5711
                        set: function (sortable) {
5712
                            var order = sortable.toArray();
5713
                            console.log(order);
5714
                        }
5715
                    },
5716
                    onEnd: function(evt) {
5717
                        console.log("onEnd");
5718
                        let list = serialize(root);
5719
                        let order = "&a=update_lp_item_order&new_order=" + JSON.stringify(list);
5720
                        $.get(
5721
                            "'.$ajax_url.'",
5722
                            order,
5723
                            function(reponse) {
5724
                                $("#message").html(reponse);
5725
                                refreshTree();
5726
                            }
5727
                        );
5728
                    },
5729
                });
5730
5731
                let resources = document.getElementsByClassName("lp_resource");
5732
                Array.prototype.forEach.call(resources, function(resource) {
5733
                    Sortable.create(resource, {
5734
                   //$(".lp_resource").sortable({
5735
                        group: "nested",
5736
                        put: ["nested-sortable"],
5737
                        filter: ".disable_drag",
5738
                        animation: 150,
5739
                        fallbackOnBody: true,
5740
                        swapThreshold: 0.65,
5741
                        dataIdAttr: "data-id",
5742
                        onRemove: function(evt) {
5743
                            console.log("onRemove");
5744
                            var itemEl = evt.item;
5745
                            var newIndex = evt.newIndex;
5746
                            var id = $(itemEl).attr("id");
5747
                            var parent_id = $(itemEl).parent().parent().attr("id");
5748
                            var type =  $(itemEl).find(".link_with_id").attr("data_type");
5749
                            var title = $(itemEl).find(".link_with_id").text();
5750
5751
                            let previousId = 0;
5752
                            if (0 !== newIndex) {
5753
                                previousId = $(itemEl).prev().attr("id");
5754
                            }
5755
                            var params = {
5756
                                "a": "add_lp_item",
5757
                                "id": id,
5758
                                "parent_id": parent_id,
5759
                                "previous_id": previousId,
5760
                                "type": type,
5761
                                "title" : title
5762
                            };
5763
                            console.log(params);
5764
                            $.ajax({
5765
                                type: "GET",
5766
                                url: "'.$ajax_url.'",
5767
                                data: params,
5768
                                success: function(itemId) {
5769
                                    $(itemEl).attr("id", itemId);
5770
                                    $(itemEl).attr("data-id", itemId);
5771
                                    let list = serialize(root);
5772
                                    let listInString = JSON.stringify(list);
5773
                                    if (typeof listInString === "undefined") {
5774
                                        listInString = "";
5775
                                    }
5776
                                    let order = "&a=update_lp_item_order&new_order=" + listInString;
5777
                                    $.get(
5778
                                        "'.$ajax_url.'",
5779
                                        order,
5780
                                        function(reponse) {
5781
                                            $("#message").html(reponse);
5782
                                            refreshTree();
5783
                                        }
5784
                                    );
5785
                                    //$("#lp_item_list").html(data);
5786
                                }
5787
                            });
5788
                        },
5789
                    });
5790
                });
5791
5792
                $("#lp_item_list2").sortable({
5793
                    items: "li",
5794
                    handle: ".moved", //only the class "moved"
5795
                    cursor: "move",
5796
                    placeholder: "ui-state-highlight", //defines the yellow highlight
5797
                    update: function(event, ui) {
5798
                        buildLPtree($("#lp_item_list"), 0);
5799
                        var order = "new_order="+ newOrderData + "&a=update_lp_item_order";
5800
                        $.get(
5801
                            "'.$ajax_url.'",
5802
                            order,
5803
                            function(reponse) {
5804
                                $("#message").html(reponse);
5805
                                order = "";
5806
                                newOrderData = "";
5807
                            }
5808
                        );
5809
                    },
5810
                    receive: function(event, ui) {
5811
                        var item = $(ui.item).find(".link_with_id");
5812
                        var id = item.attr("data_id");
5813
                        var type = item.attr("data_type");
5814
                        var title = item.attr("title");
5815
                        processReceive = true;
5816
                           //console.log(ui.item.parent().parent().attr("id"));
5817
                        if (ui.item.parent()[0]) {
5818
                            //var parent_id = $(ui.item.parent()[0]).attr("id");
5819
                            var previous_id = $(ui.item.prev()).attr("id");
5820
                            var parent_id = ui.item.parent().parent().parent().attr("id");
5821
                            if (parent_id == "undefined") {
5822
                                parent_id = 0;
5823
                            }
5824
                            console.log(parent_id);
5825
                            var params = {
5826
                                "a": "add_lp_item",
5827
                                "id": id,
5828
                                "parent_id": parent_id,
5829
                                "previous_id": previous_id,
5830
                                "type": type,
5831
                                "title" : title
5832
                            };
5833
                            console.log(params);
5834
                            $.ajax({
5835
                                type: "GET",
5836
                                url: "'.$ajax_url.'",
5837
                                data: params,
5838
                                success: function(data) {
5839
                                    //$("#scorm-list .card-body").html(data);
5840
                                    $("#lp_item_list").html(data);
5841
                                }
5842
                            });
5843
                        }
5844
                    }
5845
                });
5846
                processReceive = false;
5847
            });
5848
        </script>';
5849
5850
        $content .= "
5851
        <script>
5852
            function confirmation(name) {
5853
                if (confirm('$sureToDelete ' + name)) {
5854
                    return true;
5855
                } else {
5856
                    return false;
5857
                }
5858
            }
5859
            function refreshTree() {
5860
                var params = '&a=get_lp_item_tree';
5861
                $.get(
5862
                    '".$ajax_url."',
5863
                    params,
5864
                    function(result) {
5865
                        $('#lp_item_list').html(result);
5866
                    }
5867
                );
5868
            }
5869
5870
            $(function () {
5871
                //$('.scrollbar-inner').scrollbar();
5872
                $('#subtab').on('click', 'a:first', function() {
5873
                    window.location.reload();
5874
                });
5875
                $('#subtab ').on('click', 'a:first', function () {
5876
                    window.location.reload();
5877
                });
5878
                expandColumnToggle('#hide_bar_template', {
5879
                    selector: '#lp_sidebar'
5880
                }, {
5881
                    selector: '#doc_form'
5882
                });
5883
5884
                $('.lp-btn-associate-forum').on('click', function (e) {
5885
                    var associate = confirm('".get_lang('ConfirmAssociateForumToLPItem')."');
5886
                    if (!associate) {
5887
                        e.preventDefault();
5888
                    }
5889
                });
5890
5891
                $('.lp-btn-dissociate-forum').on('click', function (e) {
5892
                    var dissociate = confirm('".get_lang('ConfirmDissociateForumToLPItem')."');
5893
                    if (!dissociate) {
5894
                        e.preventDefault();
5895
                    }
5896
                });
5897
5898
                // hide the current template list for new documment until it tab clicked
5899
                $('#frmModel').hide();
5900
            });
5901
5902
            // document template for new document tab handler
5903
            $(document).on('shown.bs.tab', 'a[data-toggle=\"tab\"]', function (e) {
5904
                var id = e.target.id;
5905
                if (id == 'subtab2') {
5906
                    $('#frmModel').show();
5907
                } else {
5908
                    $('#frmModel').hide();
5909
                }
5910
            });
5911
5912
          function deleteItem(event) {
5913
          console.log($(event));
5914
            var id = $(event).attr('data-id');
5915
            var title = $(event).attr('data-title');
5916
            var params = '&a=delete_item&id=' + id;
5917
            if (confirmation(title)) {
5918
                $.get(
5919
                    '".$ajax_url."',
5920
                    params,
5921
                    function(result) {
5922
                        refreshTree();
5923
                    }
5924
                );
5925
            }
5926
        }
5927
        </script>";
5928
5929
        $content .= $this->return_new_tree($updateAudio, $dropElementHere);
5930
5931
        $documentId = isset($_GET['path_item']) ? (int) $_GET['path_item'] : 0;
5932
5933
        $repo = Container::getDocumentRepository();
5934
        $document = $repo->find($documentId);
5935
        if ($document) {
5936
            // Show the template list
5937
            $content .= '<div id="frmModel" class="scrollbar-inner lp-add-item"></div>';
5938
        }
5939
5940
        // Show the template list.
5941
        if (('document' === $type || 'step' === $type) && !isset($_GET['file'])) {
5942
            // Show the template list.
5943
            $content .= '<div id="frmModel" class="scrollbar-inner lp-add-item"></div>';
5944
        }
5945
5946
        return $content;
5947
    }
5948
5949
    /**
5950
     * @param bool  $updateAudio
5951
     * @param bool   $dropElement
5952
     *
5953
     * @return string
5954
     */
5955
    public function return_new_tree($updateAudio = false, $dropElement = false)
5956
    {
5957
        /*$result = $this->processBuildMenuElements($update_audio);
5958
        $list = '<ul id="lp_item_list">';
5959
        $tree = $this->print_recursive(
5960
            $result['elements'],
5961
            $result['default_data'],
5962
            $result['default_content']
5963
        );
5964
5965
        if (!empty($tree)) {
5966
            $list .= $tree;
5967
        } else {
5968
            if ($drop_element_here) {
5969
                $list .= Display::return_message(get_lang('Drag and drop an element here'));
5970
            }
5971
        }
5972
        $list .= '</ul>';*/
5973
5974
        $list = $this->getBuildTree(false, $dropElement);
5975
5976
        $return = Display::panelCollapse(
5977
            $this->name,
5978
            $list,
5979
            'scorm-list',
5980
            null,
5981
            'scorm-list-accordion',
5982
            'scorm-list-collapse'
5983
        );
5984
5985
        if ($updateAudio) {
5986
            //$return = $result['return_audio'];
5987
        }
5988
5989
        return $return;
5990
    }
5991
5992
    public function getBuildTree($noWrapper = false, $dropElement = false)
5993
    {
5994
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
5995
5996
        $upIcon = Display::return_icon(
5997
            'up.png',
5998
            get_lang('Up'),
5999
            [],
6000
            ICON_SIZE_TINY
6001
        );
6002
6003
        $disableUpIcon = Display::return_icon(
6004
            'up_na.png',
6005
            get_lang('Up'),
6006
            [],
6007
            ICON_SIZE_TINY
6008
        );
6009
6010
        $downIcon = Display::return_icon(
6011
            'down.png',
6012
            get_lang('Down'),
6013
            [],
6014
            ICON_SIZE_TINY
6015
        );
6016
6017
        $previewImage = Display::return_icon(
6018
            'preview_view.png',
6019
            get_lang('Preview'),
6020
            [],
6021
            ICON_SIZE_TINY
6022
        );
6023
        $lpItemRepo = Container::getLpItemRepository();
6024
        $itemRoot = $lpItemRepo->getItemRoot($this->get_id());
6025
6026
        $options = [
6027
            'decorate' => true,
6028
            'rootOpen' => function($tree) use ($noWrapper) {
6029
                if ($tree[0]['lvl'] === 1) {
6030
                    if ($noWrapper) {
6031
                        return '';
6032
                    }
6033
                    return '<ul id="lp_item_list" class="list-group nested-sortable">';
6034
                }
6035
6036
                return '<ul class="list-group nested-sortable">';
6037
            },
6038
            'rootClose' => function($tree) use ($noWrapper, $dropElement)  {
6039
                if ($tree[0]['lvl'] === 1) {
6040
                    if ($dropElement) {
6041
                        //return Display::return_message(get_lang('Drag and drop an element here'));
6042
                        //return $this->getDropElementHtml();
6043
                    }
6044
                    if ($noWrapper) {
6045
                        return '';
6046
                    }
6047
                }
6048
6049
                return '</ul>';
6050
            },
6051
            'childOpen' => function($child) {
6052
                $id = $child['iid'];
6053
                return '<li
6054
                    id="'.$id.'"
6055
                    data-id="'.$id.'"
6056
                    class=" flex flex-col list-group-item nested-'.$child['lvl'].'">';
6057
            },
6058
            'childClose' => '',
6059
            'nodeDecorator' => function ($node) use ($mainUrl, $previewImage, $upIcon, $downIcon) {
6060
                $fullTitle = $node['title'];
6061
                $title = cut($fullTitle, self::MAX_LP_ITEM_TITLE_LENGTH);
6062
                $itemId = $node['iid'];
6063
                $type = $node['itemType'];
6064
                $lpId = $this->get_id();
6065
6066
                $moveIcon = '';
6067
                if (TOOL_LP_FINAL_ITEM !== $type) {
6068
                    $moveIcon .= '<a class="moved" href="#">';
6069
                    $moveIcon .= Display::return_icon(
6070
                        'move_everywhere.png',
6071
                        get_lang('Move'),
6072
                        [],
6073
                        ICON_SIZE_TINY
6074
                    );
6075
                    $moveIcon .= '</a>';
6076
                }
6077
6078
                $iconName = str_replace(' ', '', $type);
6079
                $icon = Display::return_icon(
6080
                    'lp_'.$iconName.'.png',
6081
                    '',
6082
                    [],
6083
                    ICON_SIZE_TINY
6084
                );
6085
6086
                $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$itemId.'&lp_id='.$lpId;
6087
                $previewIcon = Display::url(
6088
                    $previewImage,
6089
                    $urlPreviewLink,
6090
                    [
6091
                        'target' => '_blank',
6092
                        'class' => 'btn btn-default',
6093
                        'data-title' => $title,
6094
                        'title' => $title,
6095
                    ]
6096
                );
6097
                $url = $mainUrl.'&view=build&id='.$itemId.'&lp_id='.$lpId;
6098
6099
                $preRequisitiesIcon = Display::url(
6100
                    Display::return_icon(
6101
                        'accept.png',
6102
                        get_lang('Prerequisites'),
6103
                        [],
6104
                        ICON_SIZE_TINY
6105
                    ),
6106
                    $url.'&action=edit_item_prereq',
6107
                    ['class' => 'btn btn-default']
6108
                );
6109
6110
                //$editIcon = '';
6111
                $editIcon = '<a
6112
                    href="'.$mainUrl.'&action=edit_item&view=build&id='.$itemId.'&lp_id='.$lpId.'&path_item='.$node['path'].'"
6113
                    class="btn btn-default"
6114
                    >';
6115
                $editIcon .= Display::return_icon(
6116
                    'edit.png',
6117
                    get_lang('Edit section description/name'),
6118
                    [],
6119
                    ICON_SIZE_TINY
6120
                );
6121
                $editIcon .= '</a>';
6122
                $orderIcons = '';
6123
                /*if ('final_item' !== $type) {
6124
                    $orderIcons = Display::url(
6125
                        $upIcon,
6126
                        'javascript:void(0)',
6127
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $itemId]
6128
                    );
6129
                    $orderIcons .= Display::url(
6130
                        $downIcon,
6131
                        'javascript:void(0)',
6132
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $itemId]
6133
                    );
6134
                }*/
6135
6136
                $deleteIcon = ' <a
6137
                    data-id = '.$itemId.'
6138
                    data-title = \''.addslashes($title).'\'
6139
                    href="javascript:void(0);"
6140
                    onclick="return deleteItem(this);"
6141
                    class="btn btn-default">';
6142
                $deleteIcon .= Display::return_icon(
6143
                    'delete.png',
6144
                    get_lang('Delete section'),
6145
                    [],
6146
                    ICON_SIZE_TINY
6147
                );
6148
                $deleteIcon .= '</a>';
6149
                $extra = '';
6150
6151
                if ('dir' === $type && empty($node['__children'])) {
6152
                    $level = $node['lvl'] + 1;
6153
                    $extra = '<ul class="list-group nested-sortable">
6154
                                <li class="list-group-item list-group-item-empty nested-'.$level.'"></li>
6155
                              </ul>';
6156
                }
6157
                //return $title;
6158
                $buttons = Display::tag(
6159
                    'div',
6160
                    "<div class=\"btn-group btn-group-sm\">
6161
                                $editIcon
6162
                                $preRequisitiesIcon
6163
                                $orderIcons
6164
                                $deleteIcon
6165
                               </div>",
6166
                    ['class' => 'btn-toolbar button_actions']
6167
                );
6168
                //$buttons = '';
6169
                //$extra = '';
6170
                //Display::span($title, ['title' => $fullTitle])
6171
                //return $title.                $extra;
6172
                return
6173
                    "<div class='flex flex-row'> $moveIcon  $icon <div>$title </div></div>
6174
                    $extra
6175
                    $buttons
6176
                    "
6177
                    ;
6178
            },
6179
        ];
6180
        $tree = $lpItemRepo->childrenHierarchy($itemRoot, false, $options);
6181
6182
        if (empty($tree) && $dropElement) {
6183
            return $this->getDropElementHtml($noWrapper);
6184
        }
6185
6186
        return $tree;
6187
    }
6188
6189
    public function getDropElementHtml($noWrapper = false)
6190
    {
6191
        $li = '<li class="list-group-item">'.
6192
            Display::return_message(get_lang('Drag and drop an element here')).
6193
            '</li>';
6194
        if ($noWrapper) {
6195
            return $li;
6196
        }
6197
6198
        return
6199
            '<ul id="lp_item_list" class="list-group nested-sortable">
6200
            '.$li.'
6201
            </ul>';
6202
    }
6203
6204
    /**
6205
     * @param array $elements
6206
     * @param array $default_data
6207
     * @param array $default_content
6208
     *
6209
     * @return string
6210
     */
6211
    public function print_recursive($elements, $default_data, $default_content)
6212
    {
6213
        $return = '';
6214
        foreach ($elements as $key => $item) {
6215
            if (isset($item['load_data']) || empty($item['data'])) {
6216
                $item['data'] = $default_data[$item['load_data']];
6217
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6218
            }
6219
            $sub_list = '';
6220
            if (isset($item['type']) && 'dir' === $item['type']) {
6221
                // empty value
6222
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6223
            }
6224
            if (empty($item['children'])) {
6225
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6226
                $active = null;
6227
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6228
                    $active = 'active';
6229
                }
6230
                $return .= Display::tag(
6231
                    'li',
6232
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6233
                    ['id' => $key, 'class' => 'record li_container']
6234
                );
6235
            } else {
6236
                // Sections
6237
                $data = '';
6238
                if (isset($item['children'])) {
6239
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6240
                }
6241
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6242
                $return .= Display::tag(
6243
                    'li',
6244
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6245
                    ['id' => $key, 'class' => 'record li_container']
6246
                );
6247
            }
6248
        }
6249
6250
        return $return;
6251
    }
6252
6253
    /**
6254
     * This function builds the action menu.
6255
     *
6256
     * @param bool   $returnString           Optional
6257
     * @param bool   $showRequirementButtons Optional. Allow show the requirements button
6258
     * @param bool   $isConfigPage           Optional. If is the config page, show the edit button
6259
     * @param bool   $allowExpand            Optional. Allow show the expand/contract button
6260
     * @param string $action
6261
     * @param array  $extraField
6262
     *
6263
     * @return string
6264
     */
6265
    public function build_action_menu(
6266
        $returnString = false,
6267
        $showRequirementButtons = true,
6268
        $isConfigPage = false,
6269
        $allowExpand = true,
6270
        $action = '',
6271
        $extraField = []
6272
    ) {
6273
        $actionsRight = '';
6274
        $lpId = $this->lp_id;
6275
        if (!isset($extraField['backTo']) && empty($extraField['backTo'])) {
6276
            $back = Display::url(
6277
                Display::return_icon(
6278
                    'back.png',
6279
                    get_lang('Back to learning paths'),
6280
                    '',
6281
                    ICON_SIZE_MEDIUM
6282
                ),
6283
                'lp_controller.php?'.api_get_cidreq()
6284
            );
6285
        } else {
6286
            $back = Display::url(
6287
                Display::return_icon(
6288
                    'back.png',
6289
                    get_lang('Back'),
6290
                    '',
6291
                    ICON_SIZE_MEDIUM
6292
                ),
6293
                $extraField['backTo']
6294
            );
6295
        }
6296
6297
        /*if ($backToBuild) {
6298
            $back = Display::url(
6299
                Display::return_icon(
6300
                    'back.png',
6301
                    get_lang('GoBack'),
6302
                    '',
6303
                    ICON_SIZE_MEDIUM
6304
                ),
6305
                "lp_controller.php?action=add_item&type=step&lp_id=$lpId&".api_get_cidreq()
6306
            );
6307
        }*/
6308
6309
        $actionsLeft = $back;
6310
6311
        $actionsLeft .= Display::url(
6312
            Display::return_icon(
6313
                'preview_view.png',
6314
                get_lang('Preview'),
6315
                '',
6316
                ICON_SIZE_MEDIUM
6317
            ),
6318
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6319
                'action' => 'view',
6320
                'lp_id' => $lpId,
6321
                'isStudentView' => 'true',
6322
            ])
6323
        );
6324
6325
        /*$actionsLeft .= Display::url(
6326
            Display::return_icon(
6327
                'upload_audio.png',
6328
                get_lang('Add audio'),
6329
                '',
6330
                ICON_SIZE_MEDIUM
6331
            ),
6332
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6333
                'action' => 'admin_view',
6334
                'lp_id' => $lpId,
6335
                'updateaudio' => 'true',
6336
            ])
6337
        );*/
6338
6339
        $subscriptionSettings = self::getSubscriptionSettings();
6340
6341
        $request = api_request_uri();
6342
        if (false === strpos($request, 'edit')) {
6343
            $actionsLeft .= Display::url(
6344
                Display::return_icon(
6345
                    'settings.png',
6346
                    get_lang('Course settings'),
6347
                    '',
6348
                    ICON_SIZE_MEDIUM
6349
                ),
6350
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6351
                    'action' => 'edit',
6352
                    'lp_id' => $lpId,
6353
                ])
6354
            );
6355
        }
6356
6357
        if ((false === strpos($request, 'build') &&
6358
            false === strpos($request, 'add_item')) ||
6359
            in_array($action, ['add_audio'], true)
6360
        ) {
6361
            $actionsLeft .= Display::url(
6362
                Display::return_icon(
6363
                    'edit.png',
6364
                    get_lang('Edit'),
6365
                    '',
6366
                    ICON_SIZE_MEDIUM
6367
                ),
6368
                'lp_controller.php?'.http_build_query([
6369
                    'action' => 'build',
6370
                    'lp_id' => $lpId,
6371
                ]).'&'.api_get_cidreq()
6372
            );
6373
        }
6374
6375
        if (false === strpos(api_get_self(), 'lp_subscribe_users.php')) {
6376
            if (1 == $this->subscribeUsers &&
6377
                $subscriptionSettings['allow_add_users_to_lp']) {
6378
                $actionsLeft .= Display::url(
6379
                    Display::return_icon(
6380
                        'user.png',
6381
                        get_lang('Subscribe users to learning path'),
6382
                        '',
6383
                        ICON_SIZE_MEDIUM
6384
                    ),
6385
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=$lpId&".api_get_cidreq()
6386
                );
6387
            }
6388
        }
6389
6390
        if ($allowExpand) {
6391
            /*$actionsLeft .= Display::url(
6392
                Display::return_icon(
6393
                    'expand.png',
6394
                    get_lang('Expand'),
6395
                    ['id' => 'expand'],
6396
                    ICON_SIZE_MEDIUM
6397
                ).
6398
                Display::return_icon(
6399
                    'contract.png',
6400
                    get_lang('Collapse'),
6401
                    ['id' => 'contract', 'class' => 'hide'],
6402
                    ICON_SIZE_MEDIUM
6403
                ),
6404
                '#',
6405
                ['role' => 'button', 'id' => 'hide_bar_template']
6406
            );*/
6407
        }
6408
6409
        if ($showRequirementButtons) {
6410
            $buttons = [
6411
                [
6412
                    'title' => get_lang('Set previous step as prerequisite for each step'),
6413
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6414
                        'action' => 'set_previous_step_as_prerequisite',
6415
                        'lp_id' => $lpId,
6416
                    ]),
6417
                ],
6418
                [
6419
                    'title' => get_lang('Clear all prerequisites'),
6420
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6421
                        'action' => 'clear_prerequisites',
6422
                        'lp_id' => $lpId,
6423
                    ]),
6424
                ],
6425
            ];
6426
            $actionsRight = Display::groupButtonWithDropDown(
6427
                get_lang('Prerequisites options'),
6428
                $buttons,
6429
                true
6430
            );
6431
        }
6432
6433
        if (api_is_platform_admin() && isset($extraField['authorlp'])) {
6434
            $actionsLeft .= Display::url(
6435
                Display::return_icon(
6436
                    'add-groups.png',
6437
                    get_lang('Author'),
6438
                    '',
6439
                    ICON_SIZE_MEDIUM
6440
                ),
6441
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6442
                    'action' => 'author_view',
6443
                    'lp_id' => $lpId,
6444
                ])
6445
            );
6446
        }
6447
6448
        $toolbar = Display::toolbarAction('actions-lp-controller', [$actionsLeft, $actionsRight]);
6449
6450
        if ($returnString) {
6451
            return $toolbar;
6452
        }
6453
6454
        echo $toolbar;
6455
    }
6456
6457
    /**
6458
     * Creates the default learning path folder.
6459
     *
6460
     * @param array $course
6461
     * @param int   $creatorId
6462
     *
6463
     * @return CDocument
6464
     */
6465
    public static function generate_learning_path_folder($course, $creatorId = 0)
6466
    {
6467
        // Creating learning_path folder
6468
        $dir = 'learning_path';
6469
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6470
6471
        return create_unexisting_directory(
6472
            $course,
6473
            $creatorId,
6474
            0,
6475
            null,
6476
            0,
6477
            '',
6478
            $dir,
6479
            get_lang('Learning paths'),
6480
            0
6481
        );
6482
    }
6483
6484
    /**
6485
     * @param array  $course
6486
     * @param string $lp_name
6487
     * @param int    $creatorId
6488
     *
6489
     * @return CDocument
6490
     */
6491
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6492
    {
6493
        $filepath = '';
6494
        $dir = '/learning_path/';
6495
6496
        if (empty($lp_name)) {
6497
            $lp_name = $this->name;
6498
        }
6499
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6500
        $parent = self::generate_learning_path_folder($course, $creatorId);
6501
6502
        // Limits title size
6503
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6504
        $dir = $dir.$title;
6505
6506
        // Creating LP folder
6507
        $documentId = null;
6508
        $folder = null;
6509
        if ($parent) {
6510
            $folder = create_unexisting_directory(
6511
                $course,
6512
                $creatorId,
6513
                0,
6514
                0,
6515
                0,
6516
                $filepath,
6517
                $dir,
6518
                $lp_name,
6519
                '',
6520
                false,
6521
                false,
6522
                $parent
6523
            );
6524
        }
6525
6526
        return $folder;
6527
    }
6528
6529
    /**
6530
     * Create a new document //still needs some finetuning.
6531
     *
6532
     * @param array  $courseInfo
6533
     * @param string $content
6534
     * @param string $title
6535
     * @param string $extension
6536
     * @param int    $parentId
6537
     * @param int    $creatorId  creator id
6538
     *
6539
     * @return int
6540
     */
6541
    public function create_document(
6542
        $courseInfo,
6543
        $content = '',
6544
        $title = '',
6545
        $extension = 'html',
6546
        $parentId = 0,
6547
        $creatorId = 0
6548
    ) {
6549
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6550
        $sessionId = api_get_session_id();
6551
6552
        // Generates folder
6553
        $this->generate_lp_folder($courseInfo);
6554
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6555
        // is already escaped twice when it gets here.
6556
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6557
        if (!empty($title)) {
6558
            $title = api_replace_dangerous_char(stripslashes($title));
6559
        } else {
6560
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6561
        }
6562
6563
        $title = disable_dangerous_file($title);
6564
        $filename = $title;
6565
        $tmp_filename = $filename;
6566
        /*$i = 0;
6567
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
6568
            $tmp_filename = $filename.'_'.++$i;
6569
        }*/
6570
        $filename = $tmp_filename.'.'.$extension;
6571
6572
        if ('html' === $extension) {
6573
            $content = stripslashes($content);
6574
            $content = str_replace(
6575
                api_get_path(WEB_COURSE_PATH),
6576
                api_get_path(REL_PATH).'courses/',
6577
                $content
6578
            );
6579
6580
            // Change the path of mp3 to absolute.
6581
            // The first regexp deals with :// urls.
6582
            /*$content = preg_replace(
6583
                "|(flashvars=\"file=)([^:/]+)/|",
6584
                "$1".api_get_path(
6585
                    REL_COURSE_PATH
6586
                ).$courseInfo['path'].'/document/',
6587
                $content
6588
            );*/
6589
            // The second regexp deals with audio/ urls.
6590
            /*$content = preg_replace(
6591
                "|(flashvars=\"file=)([^/]+)/|",
6592
                "$1".api_get_path(
6593
                    REL_COURSE_PATH
6594
                ).$courseInfo['path'].'/document/$2/',
6595
                $content
6596
            );*/
6597
            // For flv player: To prevent edition problem with firefox,
6598
            // we have to use a strange tip (don't blame me please).
6599
            $content = str_replace(
6600
                '</body>',
6601
                '<style type="text/css">body{}</style></body>',
6602
                $content
6603
            );
6604
        }
6605
6606
        $document = DocumentManager::addDocument(
6607
            $courseInfo,
6608
            null,
6609
            'file',
6610
            '',
6611
            $tmp_filename,
6612
            '',
6613
            0, //readonly
6614
            true,
6615
            null,
6616
            $sessionId,
6617
            $creatorId,
6618
            false,
6619
            $content,
6620
            $parentId
6621
        );
6622
6623
        $document_id = $document->getIid();
6624
        if ($document_id) {
6625
            $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
6626
            $new_title = $originalTitle;
6627
6628
            if ($new_comment || $new_title) {
6629
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
6630
                $ct = '';
6631
                if ($new_comment) {
6632
                    $ct .= ", comment='".Database::escape_string($new_comment)."'";
6633
                }
6634
                if ($new_title) {
6635
                    $ct .= ", title='".Database::escape_string($new_title)."' ";
6636
                }
6637
6638
                $sql = "UPDATE $tbl_doc SET ".substr($ct, 1)."
6639
                        WHERE iid = $document_id ";
6640
                Database::query($sql);
6641
            }
6642
        }
6643
6644
        return $document_id;
6645
    }
6646
6647
    /**
6648
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
6649
     */
6650
    public function edit_document()
6651
    {
6652
        $repo = Container::getDocumentRepository();
6653
        if (isset($_REQUEST['document_id']) && !empty($_REQUEST['document_id'])) {
6654
            $id = (int) $_REQUEST['document_id'];
6655
            /** @var CDocument $document */
6656
            $document = $repo->find($id);
6657
            if ($document->getResourceNode()->hasEditableTextContent()) {
6658
                $repo->updateResourceFileContent($document, $_REQUEST['content_lp']);
6659
                /*   $nodeRepo = Container::getDocumentRepository()->getResourceNodeRepository();
6660
                   $nodeRepo->update($document->getResourceNode());
6661
                   var_dump($document->getResourceNode()->getContent());
6662
                   exit;*/
6663
            }
6664
            $document->setTitle($_REQUEST['title']);
6665
            $repo->update($document);
6666
        }
6667
    }
6668
6669
    /**
6670
     * Displays the selected item, with a panel for manipulating the item.
6671
     *
6672
     * @param CLpItem $lpItem
6673
     * @param string  $msg
6674
     * @param bool    $show_actions
6675
     *
6676
     * @return string
6677
     */
6678
    public function display_item($lpItem, $msg = null, $show_actions = true)
6679
    {
6680
        $course_id = api_get_course_int_id();
6681
        $return = '';
6682
6683
        if (null === $lpItem) {
6684
            return '';
6685
        }
6686
        $item_id = $lpItem->getIid();
6687
        $itemType = $lpItem->getItemType();
6688
        $lpId = $lpItem->getLp()->getIid();
6689
        $path = $lpItem->getPath();
6690
6691
        Session::write('parent_item_id', 'dir' === $itemType ? $item_id : 0);
6692
6693
        // Prevents wrong parent selection for document, see Bug#1251.
6694
        if ('dir' !== $itemType) {
6695
            Session::write('parent_item_id', $lpItem->getParentItemId());
6696
        }
6697
6698
        if ($show_actions) {
6699
            $return .= $this->displayItemMenu($lpItem);
6700
        }
6701
        $return .= '<div style="padding:10px;">';
6702
6703
        if ('' != $msg) {
6704
            $return .= $msg;
6705
        }
6706
6707
        $return .= '<h3>'.$lpItem->getTitle().'</h3>';
6708
6709
        switch ($itemType) {
6710
            case TOOL_THREAD:
6711
                $link = $this->rl_get_resource_link_for_learnpath(
6712
                    $course_id,
6713
                    $lpId,
6714
                    $item_id,
6715
                    0
6716
                );
6717
                $return .= Display::url(
6718
                    get_lang('Go to thread'),
6719
                    $link,
6720
                    ['class' => 'btn btn-primary']
6721
                );
6722
                break;
6723
            case TOOL_FORUM:
6724
                $return .= Display::url(
6725
                    get_lang('Go to the forum'),
6726
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$path,
6727
                    ['class' => 'btn btn-primary']
6728
                );
6729
                break;
6730
            case TOOL_QUIZ:
6731
                if (!empty($path)) {
6732
                    $exercise = new Exercise();
6733
                    $exercise->read($path);
6734
                    $return .= $exercise->description.'<br />';
6735
                    $return .= Display::url(
6736
                        get_lang('Go to exercise'),
6737
                        api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
6738
                        ['class' => 'btn btn-primary']
6739
                    );
6740
                }
6741
                break;
6742
            case TOOL_LP_FINAL_ITEM:
6743
                $return .= $this->getSavedFinalItem();
6744
                break;
6745
            case TOOL_DOCUMENT:
6746
            case TOOL_READOUT_TEXT:
6747
                $repo = Container::getDocumentRepository();
6748
                /** @var CDocument $document */
6749
                $document = $repo->find($lpItem->getPath());
6750
                $return .= $this->display_document($document, true, true);
6751
                break;
6752
            case TOOL_HOTPOTATOES:
6753
                $return .= $this->display_document($document, false, true);
6754
                break;
6755
        }
6756
        $return .= '</div>';
6757
6758
        return $return;
6759
    }
6760
6761
    /**
6762
     * Shows the needed forms for editing a specific item.
6763
     *
6764
     * @param CLpItem $lpItem
6765
     *
6766
     * @throws Exception
6767
     *
6768
     *
6769
     * @return string
6770
     */
6771
    public function display_edit_item($lpItem, $excludeExtraFields = [])
6772
    {
6773
        $return = '';
6774
        if (empty($lpItem)) {
6775
            return '';
6776
        }
6777
        $itemType = $lpItem->getItemType();
6778
        $path = $lpItem->getPath();
6779
6780
        switch ($itemType) {
6781
            case 'dir':
6782
            case 'asset':
6783
            case 'sco':
6784
                if (isset($_GET['view']) && 'build' === $_GET['view']) {
6785
                    $return .= $this->displayItemMenu($lpItem);
6786
                    $return .= $this->display_item_form($lpItem, 'edit');
6787
                } else {
6788
                    $return .= $this->display_item_form($lpItem, 'edit_item');
6789
                }
6790
                break;
6791
            case TOOL_LP_FINAL_ITEM:
6792
            case TOOL_DOCUMENT:
6793
            case TOOL_READOUT_TEXT:
6794
                $return .= $this->displayItemMenu($lpItem);
6795
                $return .= $this->displayDocumentForm('edit', $lpItem);
6796
                break;
6797
            case TOOL_LINK:
6798
                $link = null;
6799
                if (!empty($path)) {
6800
                    $repo = Container::getLinkRepository();
6801
                    $link = $repo->find($path);
6802
                }
6803
                $return .= $this->displayItemMenu($lpItem);
6804
                $return .= $this->display_link_form('edit', $lpItem, $link);
6805
6806
                break;
6807
            case TOOL_QUIZ:
6808
                if (!empty($path)) {
6809
                    $repo = Container::getQuizRepository();
6810
                    $resource = $repo->find($path);
6811
                }
6812
                $return .= $this->displayItemMenu($lpItem);
6813
                $return .= $this->display_quiz_form('edit', $lpItem, $resource);
6814
                break;
6815
            /*case TOOL_HOTPOTATOES:
6816
                $return .= $this->displayItemMenu($lpItem);
6817
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
6818
                break;*/
6819
            case TOOL_STUDENTPUBLICATION:
6820
                if (!empty($path)) {
6821
                    $repo = Container::getStudentPublicationRepository();
6822
                    $resource = $repo->find($path);
6823
                }
6824
                $return .= $this->displayItemMenu($lpItem);
6825
                $return .= $this->display_student_publication_form('edit', $lpItem, $resource);
6826
                break;
6827
            case TOOL_FORUM:
6828
                if (!empty($path)) {
6829
                    $repo = Container::getForumRepository();
6830
                    $resource = $repo->find($path);
6831
                }
6832
                $return .= $this->displayItemMenu($lpItem);
6833
                $return .= $this->display_forum_form('edit', $lpItem, $resource);
6834
                break;
6835
            case TOOL_THREAD:
6836
                if (!empty($path)) {
6837
                    $repo = Container::getForumPostRepository();
6838
                    $resource = $repo->find($path);
6839
                }
6840
                $return .= $this->displayItemMenu($lpItem);
6841
                $return .= $this->display_thread_form('edit', $lpItem, $resource);
6842
                break;
6843
        }
6844
6845
        return $return;
6846
    }
6847
6848
    /**
6849
     * Function that displays a list with al the resources that
6850
     * could be added to the learning path.
6851
     *
6852
     * @throws Exception
6853
     */
6854
    public function displayResources(): string
6855
    {
6856
        // Get all the docs.
6857
        $documents = $this->get_documents(true);
6858
6859
        // Get all the exercises.
6860
        $exercises = $this->get_exercises();
6861
6862
        // Get all the links.
6863
        $links = $this->get_links();
6864
6865
        // Get all the student publications.
6866
        $works = $this->get_student_publications();
6867
6868
        // Get all the forums.
6869
        $forums = $this->get_forums();
6870
6871
        // Get the final item form (see BT#11048) .
6872
        $finish = $this->getFinalItemForm();
6873
        $size = ICON_SIZE_MEDIUM; //ICON_SIZE_BIG
6874
        $headers = [
6875
            Display::return_icon('folder_document.png', get_lang('Documents'), [], $size),
6876
            Display::return_icon('quiz.png', get_lang('Tests'), [], $size),
6877
            Display::return_icon('links.png', get_lang('Links'), [], $size),
6878
            Display::return_icon('works.png', get_lang('Assignments'), [], $size),
6879
            Display::return_icon('forum.png', get_lang('Forums'), [], $size),
6880
            Display::return_icon('add_learnpath_section.png', get_lang('Add section'), [], $size),
6881
            Display::return_icon('certificate.png', get_lang('Certificate'), [], $size),
6882
        ];
6883
6884
        $content = Display::return_message(
6885
            get_lang('Click on the [Learner view] button to see your learning path'),
6886
            'normal'
6887
        );
6888
        $section = $this->displayNewSectionForm();
6889
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
6890
6891
        $content .= Display::tabs(
6892
            $headers,
6893
            [
6894
                $documents,
6895
                $exercises,
6896
                $links,
6897
                $works,
6898
                $forums,
6899
                $section,
6900
                $finish,
6901
            ],
6902
            'resource_tab',
6903
            [],
6904
            [],
6905
            $selected
6906
        );
6907
6908
        return $content;
6909
    }
6910
6911
    /**
6912
     * Returns the extension of a document.
6913
     *
6914
     * @param string $filename
6915
     *
6916
     * @return string Extension (part after the last dot)
6917
     */
6918
    public function get_extension($filename)
6919
    {
6920
        $explode = explode('.', $filename);
6921
6922
        return $explode[count($explode) - 1];
6923
    }
6924
6925
    /**
6926
     * @return string
6927
     */
6928
    public function getCurrentBuildingModeURL()
6929
    {
6930
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
6931
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
6932
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
6933
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
6934
6935
        $currentUrl = api_get_self().'?'.api_get_cidreq().
6936
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
6937
6938
        return $currentUrl;
6939
    }
6940
6941
    /**
6942
     * Displays a document by id.
6943
     *
6944
     * @param CDocument $document
6945
     * @param bool      $show_title
6946
     * @param bool      $iframe
6947
     * @param bool      $edit_link
6948
     *
6949
     * @return string
6950
     */
6951
    public function display_document($document, $show_title = false, $iframe = true, $edit_link = false)
6952
    {
6953
        $return = '';
6954
        if (!$document) {
6955
            return '';
6956
        }
6957
6958
        $repo = Container::getDocumentRepository();
6959
6960
        // TODO: Add a path filter.
6961
        if ($iframe) {
6962
            //$url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.str_replace('%2F', '/', urlencode($row_doc['path'])).'?'.api_get_cidreq();
6963
            $url = $repo->getResourceFileUrl($document);
6964
6965
            $return .= '<iframe
6966
                id="learnpath_preview_frame"
6967
                frameborder="0"
6968
                height="400"
6969
                width="100%"
6970
                scrolling="auto"
6971
                src="'.$url.'"></iframe>';
6972
        } else {
6973
            $return = $repo->getResourceFileContent($document);
6974
        }
6975
6976
        return $return;
6977
    }
6978
6979
    /**
6980
     * Return HTML form to add/edit a link item.
6981
     *
6982
     * @param string  $action (add/edit)
6983
     * @param CLpItem $lpItem
6984
     * @param CLink   $link
6985
     *
6986
     * @throws Exception
6987
     *
6988
     *
6989
     * @return string HTML form
6990
     */
6991
    public function display_link_form($action, $lpItem, $link)
6992
    {
6993
        $item_url = '';
6994
        if ($link) {
6995
            $item_url = stripslashes($link->getUrl());
6996
        }
6997
        $form = new FormValidator(
6998
            'edit_link',
6999
            'POST',
7000
            $this->getCurrentBuildingModeURL()
7001
        );
7002
7003
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7004
7005
        $urlAttributes = ['class' => 'learnpath_item_form'];
7006
        $urlAttributes['disabled'] = 'disabled';
7007
        $form->addElement('url', 'url', get_lang('URL'), $urlAttributes);
7008
        $form->setDefault('url', $item_url);
7009
7010
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7011
7012
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7013
    }
7014
7015
    /**
7016
     * Return HTML form to add/edit a quiz.
7017
     *
7018
     * @param string  $action   Action (add/edit)
7019
     * @param CLpItem $lpItem   Item ID if already exists
7020
     * @param CQuiz   $exercise Extra information (quiz ID if integer)
7021
     *
7022
     * @throws Exception
7023
     *
7024
     * @return string HTML form
7025
     */
7026
    public function display_quiz_form($action, $lpItem, $exercise)
7027
    {
7028
        $form = new FormValidator(
7029
            'quiz_form',
7030
            'POST',
7031
            $this->getCurrentBuildingModeURL()
7032
        );
7033
7034
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7035
7036
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7037
7038
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7039
    }
7040
7041
    /**
7042
     * Return the form to display the forum edit/add option.
7043
     *
7044
     * @param CLpItem $lpItem
7045
     *
7046
     * @throws Exception
7047
     *
7048
     * @return string HTML form
7049
     */
7050
    public function display_forum_form($action, $lpItem, $resource)
7051
    {
7052
        $form = new FormValidator(
7053
            'forum_form',
7054
            'POST',
7055
            $this->getCurrentBuildingModeURL()
7056
        );
7057
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7058
7059
        if ('add' === $action) {
7060
            $form->addButtonSave(get_lang('Add forum to course'), 'submit_button');
7061
        } else {
7062
            $form->addButtonSave(get_lang('Edit the current forum'), 'submit_button');
7063
        }
7064
7065
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7066
    }
7067
7068
    /**
7069
     * Return HTML form to add/edit forum threads.
7070
     *
7071
     * @param string  $action
7072
     * @param CLpItem $lpItem
7073
     * @param string  $resource
7074
     *
7075
     * @throws Exception
7076
     *
7077
     * @return string HTML form
7078
     */
7079
    public function display_thread_form($action, $lpItem, $resource)
7080
    {
7081
        $form = new FormValidator(
7082
            'thread_form',
7083
            'POST',
7084
            $this->getCurrentBuildingModeURL()
7085
        );
7086
7087
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7088
7089
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7090
7091
        return $form->returnForm();
7092
    }
7093
7094
    /**
7095
     * Return the HTML form to display an item (generally a dir item).
7096
     *
7097
     * @param CLpItem $lpItem
7098
     * @param string  $action
7099
     *
7100
     * @throws Exception
7101
     *
7102
     *
7103
     * @return string HTML form
7104
     */
7105
    public function display_item_form(
7106
        $lpItem,
7107
        $action = 'add_item'
7108
    ) {
7109
        $item_type = $lpItem->getItemType();
7110
7111
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
7112
7113
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
7114
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7115
7116
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
7117
7118
        return $form->returnForm();
7119
    }
7120
7121
    /**
7122
     * Return HTML form to add/edit a student publication (work).
7123
     *
7124
     * @param string              $action
7125
     * @param CStudentPublication $resource
7126
     *
7127
     * @throws Exception
7128
     *
7129
     * @return string HTML form
7130
     */
7131
    public function display_student_publication_form($action, CLpItem $lpItem, $resource)
7132
    {
7133
        $form = new FormValidator('frm_student_publication', 'post', '#');
7134
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7135
7136
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7137
7138
        $return = '<div class="sectioncomment">';
7139
        $return .= $form->returnForm();
7140
        $return .= '</div>';
7141
7142
        return $return;
7143
    }
7144
7145
    public function displayNewSectionForm()
7146
    {
7147
        $action = 'add_item';
7148
        $item_type = 'dir';
7149
7150
        $lpItem = new CLpItem();
7151
        $lpItem->setTitle('dir');
7152
        $lpItem->setItemType('dir');
7153
7154
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
7155
7156
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
7157
        LearnPathItemForm::setForm($form, 'add', $this, $lpItem);
7158
7159
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
7160
        $form->addElement('hidden', 'type', 'dir');
7161
7162
        return $form->returnForm();
7163
    }
7164
7165
    /**
7166
     * Returns the form to update or create a document.
7167
     *
7168
     * @param string  $action (add/edit)
7169
     * @param CLpItem $lpItem
7170
     *
7171
     *
7172
     * @throws Exception
7173
     *
7174
     * @return string HTML form
7175
     */
7176
    public function displayDocumentForm($action = 'add', $lpItem = null)
7177
    {
7178
        $courseInfo = api_get_course_info();
7179
7180
        $form = new FormValidator(
7181
            'form',
7182
            'POST',
7183
            $this->getCurrentBuildingModeURL(),
7184
            '',
7185
            ['enctype' => 'multipart/form-data']
7186
        );
7187
7188
        $data = $this->generate_lp_folder($courseInfo);
7189
7190
        if (null !== $lpItem) {
7191
            LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7192
        }
7193
7194
        switch ($action) {
7195
            case 'add':
7196
                $folders = DocumentManager::get_all_document_folders(
7197
                    $courseInfo,
7198
                    0,
7199
                    true
7200
                );
7201
                DocumentManager::build_directory_selector(
7202
                    $folders,
7203
                    '',
7204
                    [],
7205
                    true,
7206
                    $form,
7207
                    'directory_parent_id'
7208
                );
7209
7210
                if ($data) {
7211
                    $defaults['directory_parent_id'] = $data->getIid();
7212
                }
7213
7214
                break;
7215
        }
7216
7217
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7218
7219
        return $form->returnForm();
7220
    }
7221
7222
    /**
7223
     * @param array  $courseInfo
7224
     * @param string $content
7225
     * @param string $title
7226
     * @param int    $parentId
7227
     *
7228
     * @throws \Doctrine\ORM\ORMException
7229
     * @throws \Doctrine\ORM\OptimisticLockException
7230
     * @throws \Doctrine\ORM\TransactionRequiredException
7231
     *
7232
     * @return int
7233
     */
7234
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
7235
    {
7236
        $creatorId = api_get_user_id();
7237
        $sessionId = api_get_session_id();
7238
7239
        // Generates folder
7240
        $result = $this->generate_lp_folder($courseInfo);
7241
        $dir = $result['dir'];
7242
7243
        if (empty($parentId) || '/' == $parentId) {
7244
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
7245
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
7246
7247
            if ('/' === $parentId) {
7248
                $dir = '/';
7249
            }
7250
7251
            // Please, do not modify this dirname formatting.
7252
            if (strstr($dir, '..')) {
7253
                $dir = '/';
7254
            }
7255
7256
            if (!empty($dir[0]) && '.' == $dir[0]) {
7257
                $dir = substr($dir, 1);
7258
            }
7259
            if (!empty($dir[0]) && '/' != $dir[0]) {
7260
                $dir = '/'.$dir;
7261
            }
7262
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
7263
                $dir .= '/';
7264
            }
7265
        } else {
7266
            $parentInfo = DocumentManager::get_document_data_by_id(
7267
                $parentId,
7268
                $courseInfo['code']
7269
            );
7270
            if (!empty($parentInfo)) {
7271
                $dir = $parentInfo['path'].'/';
7272
            }
7273
        }
7274
7275
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7276
7277
        if (!is_dir($filepath)) {
7278
            $dir = '/';
7279
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7280
        }
7281
7282
        $originalTitle = !empty($title) ? $title : $_POST['title'];
7283
7284
        if (!empty($title)) {
7285
            $title = api_replace_dangerous_char(stripslashes($title));
7286
        } else {
7287
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
7288
        }
7289
7290
        $title = disable_dangerous_file($title);
7291
        $filename = $title;
7292
        $content = !empty($content) ? $content : $_POST['content_lp'];
7293
        $tmpFileName = $filename;
7294
7295
        $i = 0;
7296
        while (file_exists($filepath.$tmpFileName.'.html')) {
7297
            $tmpFileName = $filename.'_'.++$i;
7298
        }
7299
7300
        $filename = $tmpFileName.'.html';
7301
        $content = stripslashes($content);
7302
7303
        if (file_exists($filepath.$filename)) {
7304
            return 0;
7305
        }
7306
7307
        $putContent = file_put_contents($filepath.$filename, $content);
7308
7309
        if (false === $putContent) {
7310
            return 0;
7311
        }
7312
7313
        $fileSize = filesize($filepath.$filename);
7314
        $saveFilePath = $dir.$filename;
7315
7316
        $document = DocumentManager::addDocument(
7317
            $courseInfo,
7318
            $saveFilePath,
7319
            'file',
7320
            $fileSize,
7321
            $tmpFileName,
7322
            '',
7323
            0, //readonly
7324
            true,
7325
            null,
7326
            $sessionId,
7327
            $creatorId
7328
        );
7329
7330
        $documentId = $document->getId();
7331
7332
        if (!$document) {
7333
            return 0;
7334
        }
7335
7336
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7337
        $newTitle = $originalTitle;
7338
7339
        if ($newComment || $newTitle) {
7340
            $em = Database::getManager();
7341
7342
            if ($newComment) {
7343
                $document->setComment($newComment);
7344
            }
7345
7346
            if ($newTitle) {
7347
                $document->setTitle($newTitle);
7348
            }
7349
7350
            $em->persist($document);
7351
            $em->flush();
7352
        }
7353
7354
        return $documentId;
7355
    }
7356
7357
    /**
7358
     * Displays the menu for manipulating a step.
7359
     *
7360
     * @return string
7361
     */
7362
    public function displayItemMenu(CLpItem $lpItem)
7363
    {
7364
        $item_id = $lpItem->getIid();
7365
        $audio = $lpItem->getAudio();
7366
        $itemType = $lpItem->getItemType();
7367
        $path = $lpItem->getPath();
7368
7369
        $return = '';
7370
        $audio_player = null;
7371
        // We display an audio player if needed.
7372
        if (!empty($audio)) {
7373
            /*$webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
7374
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
7375
                .'<audio src="'.$webAudioPath.'" controls>'
7376
                .'</div><br>';*/
7377
        }
7378
7379
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
7380
7381
        if (TOOL_LP_FINAL_ITEM !== $itemType) {
7382
            $return .= Display::url(
7383
                Display::return_icon(
7384
                    'edit.png',
7385
                    get_lang('Edit'),
7386
                    [],
7387
                    ICON_SIZE_SMALL
7388
                ),
7389
                $url.'&action=edit_item&path_item='.$path
7390
            );
7391
7392
            /*$return .= Display::url(
7393
                Display::return_icon(
7394
                    'move.png',
7395
                    get_lang('Move'),
7396
                    [],
7397
                    ICON_SIZE_SMALL
7398
                ),
7399
                $url.'&action=move_item'
7400
            );*/
7401
        }
7402
7403
        // Commented for now as prerequisites cannot be added to chapters.
7404
        if ('dir' !== $itemType) {
7405
            $return .= Display::url(
7406
                Display::return_icon(
7407
                    'accept.png',
7408
                    get_lang('Prerequisites'),
7409
                    [],
7410
                    ICON_SIZE_SMALL
7411
                ),
7412
                $url.'&action=edit_item_prereq'
7413
            );
7414
        }
7415
        $return .= Display::url(
7416
            Display::return_icon(
7417
                'delete.png',
7418
                get_lang('Delete'),
7419
                [],
7420
                ICON_SIZE_SMALL
7421
            ),
7422
            $url.'&action=delete_item'
7423
        );
7424
7425
        /*if (in_array($itemType, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
7426
            $documentData = DocumentManager::get_document_data_by_id($path, $course_code);
7427
            if (empty($documentData)) {
7428
                // Try with iid
7429
                $table = Database::get_course_table(TABLE_DOCUMENT);
7430
                $sql = "SELECT path FROM $table
7431
                        WHERE
7432
                              c_id = ".api_get_course_int_id()." AND
7433
                              iid = ".$path." AND
7434
                              path NOT LIKE '%_DELETED_%'";
7435
                $result = Database::query($sql);
7436
                $documentData = Database::fetch_array($result);
7437
                if ($documentData) {
7438
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
7439
                }
7440
            }
7441
            if (isset($documentData['absolute_path_from_document'])) {
7442
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
7443
            }
7444
        }*/
7445
7446
        if (!empty($audio_player)) {
7447
            $return .= $audio_player;
7448
        }
7449
7450
        return Display::toolbarAction('lp_item', [$return]);
7451
    }
7452
7453
    /**
7454
     * Creates the javascript needed for filling up the checkboxes without page reload.
7455
     *
7456
     * @return string
7457
     */
7458
    public function get_js_dropdown_array()
7459
    {
7460
        $course_id = api_get_course_int_id();
7461
        $return = 'var child_name = new Array();'."\n";
7462
        $return .= 'var child_value = new Array();'."\n\n";
7463
        $return .= 'child_name[0] = new Array();'."\n";
7464
        $return .= 'child_value[0] = new Array();'."\n\n";
7465
7466
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7467
        $sql = "SELECT * FROM ".$tbl_lp_item."
7468
                WHERE
7469
                    lp_id = ".$this->lp_id." AND
7470
                    parent_item_id = 0
7471
                ORDER BY display_order ASC";
7472
        Database::query($sql);
7473
        $i = 0;
7474
7475
        $list = $this->getItemsForForm(true);
7476
7477
        foreach ($list as $row_zero) {
7478
            if (TOOL_LP_FINAL_ITEM !== $row_zero['item_type']) {
7479
                if (TOOL_QUIZ == $row_zero['item_type']) {
7480
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
7481
                }
7482
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
7483
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
7484
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
7485
            }
7486
        }
7487
7488
        $return .= "\n";
7489
        $sql = "SELECT * FROM $tbl_lp_item
7490
                WHERE lp_id = ".$this->lp_id;
7491
        $res = Database::query($sql);
7492
        while ($row = Database::fetch_array($res)) {
7493
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
7494
                           WHERE
7495
                                parent_item_id = ".$row['iid']."
7496
                           ORDER BY display_order ASC";
7497
            $res_parent = Database::query($sql_parent);
7498
            $i = 0;
7499
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
7500
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
7501
7502
            while ($row_parent = Database::fetch_array($res_parent)) {
7503
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
7504
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
7505
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
7506
            }
7507
            $return .= "\n";
7508
        }
7509
7510
        $return .= "
7511
            function load_cbo(id) {
7512
                if (!id) {
7513
                    return false;
7514
                }
7515
7516
                var cbo = document.getElementById('previous');
7517
                for(var i = cbo.length - 1; i > 0; i--) {
7518
                    cbo.options[i] = null;
7519
                }
7520
7521
                var k=0;
7522
                for(var i = 1; i <= child_name[id].length; i++){
7523
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
7524
                    option.style.paddingLeft = '40px';
7525
                    cbo.options[i] = option;
7526
                    k = i;
7527
                }
7528
7529
                cbo.options[k].selected = true;
7530
                //$('#previous').selectpicker('refresh');
7531
            }";
7532
7533
        return $return;
7534
    }
7535
7536
    /**
7537
     * Display the form to allow moving an item.
7538
     *
7539
     * @param CLpItem $lpItem
7540
     *
7541
     * @throws Exception
7542
     *
7543
     *
7544
     * @return string HTML form
7545
     */
7546
    public function display_move_item($lpItem)
7547
    {
7548
        $return = '';
7549
        $path = $lpItem->getPath();
7550
7551
        if ($lpItem) {
7552
            $itemType = $lpItem->getItemType();
7553
            switch ($itemType) {
7554
                case 'dir':
7555
                case 'asset':
7556
                    $return .= $this->displayItemMenu($lpItem);
7557
                    $return .= $this->display_item_form(
7558
                        $lpItem,
7559
                        get_lang('Move the current section'),
7560
                        'move',
7561
                        $row
7562
                    );
7563
                    break;
7564
                case TOOL_DOCUMENT:
7565
                    $return .= $this->displayItemMenu($lpItem);
7566
                    $return .= $this->displayDocumentForm('move', $lpItem);
7567
                    break;
7568
                case TOOL_LINK:
7569
                    $link = null;
7570
                    if (!empty($path)) {
7571
                        $repo = Container::getLinkRepository();
7572
                        $link = $repo->find($path);
7573
                    }
7574
                    $return .= $this->displayItemMenu($lpItem);
7575
                    $return .= $this->display_link_form('move', $lpItem, $link);
7576
                    break;
7577
                case TOOL_HOTPOTATOES:
7578
                    $return .= $this->displayItemMenu($lpItem);
7579
                    $return .= $this->display_link_form('move', $lpItem, $row);
7580
                    break;
7581
                case TOOL_QUIZ:
7582
                    $return .= $this->displayItemMenu($lpItem);
7583
                    $return .= $this->display_quiz_form('move', $lpItem, $row);
7584
                    break;
7585
                case TOOL_STUDENTPUBLICATION:
7586
                    $return .= $this->displayItemMenu($lpItem);
7587
                    $return .= $this->display_student_publication_form('move', $lpItem, $row);
7588
                    break;
7589
                case TOOL_FORUM:
7590
                    $return .= $this->displayItemMenu($lpItem);
7591
                    $return .= $this->display_forum_form('move', $lpItem, $row);
7592
                    break;
7593
                case TOOL_THREAD:
7594
                    $return .= $this->displayItemMenu($lpItem);
7595
                    $return .= $this->display_forum_form('move', $lpItem, $row);
7596
                    break;
7597
            }
7598
        }
7599
7600
        return $return;
7601
    }
7602
7603
    /**
7604
     * Return HTML form to allow prerequisites selection.
7605
     *
7606
     * @todo use FormValidator
7607
     *
7608
     * @return string HTML form
7609
     */
7610
    public function display_item_prerequisites_form(CLpItem $lpItem)
7611
    {
7612
        $course_id = api_get_course_int_id();
7613
        $preRequisiteId = $lpItem->getPrerequisite();
7614
        $itemId = $lpItem->getIid();
7615
7616
        $return = Display::page_header(get_lang('Add/edit prerequisites').' '.$lpItem->getTitle());
7617
7618
        $return .= '<form method="POST">';
7619
        $return .= '<div class="table-responsive">';
7620
        $return .= '<table class="table table-hover">';
7621
        $return .= '<thead>';
7622
        $return .= '<tr>';
7623
        $return .= '<th>'.get_lang('Prerequisites').'</th>';
7624
        $return .= '<th width="140">'.get_lang('minimum').'</th>';
7625
        $return .= '<th width="140">'.get_lang('maximum').'</th>';
7626
        $return .= '</tr>';
7627
        $return .= '</thead>';
7628
7629
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
7630
        $return .= '<tbody>';
7631
        $return .= '<tr>';
7632
        $return .= '<td colspan="3">';
7633
        $return .= '<div class="radio learnpath"><label for="idnone">';
7634
        $return .= '<input checked="checked" id="idnone" name="prerequisites" type="radio" />';
7635
        $return .= get_lang('none').'</label>';
7636
        $return .= '</div>';
7637
        $return .= '</tr>';
7638
7639
        // @todo use entitites
7640
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7641
        $sql = "SELECT * FROM $tbl_lp_item
7642
                WHERE lp_id = ".$this->lp_id;
7643
        $result = Database::query($sql);
7644
7645
        $selectedMinScore = [];
7646
        $selectedMaxScore = [];
7647
        $masteryScore = [];
7648
        while ($row = Database::fetch_array($result)) {
7649
            if ($row['iid'] == $itemId) {
7650
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
7651
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
7652
            }
7653
            $masteryScore[$row['iid']] = $row['mastery_score'];
7654
        }
7655
7656
        $displayOrder = $lpItem->getDisplayOrder();
7657
        $lpItemRepo = Container::getLpItemRepository();
7658
        $itemRoot = $lpItemRepo->getItemRoot($this->get_id());
7659
        $em = Database::getManager();
7660
7661
        $currentItemId = $itemId;
7662
        $options = [
7663
            'decorate' => true,
7664
            'rootOpen' => function() {
7665
                return '';
7666
            },
7667
            'rootClose' => function() {
7668
                return '';
7669
            },
7670
            'childOpen' => function() {
7671
                return '';
7672
            },
7673
            'childClose' => '',
7674
            'nodeDecorator' => function ($item) use (
7675
                $currentItemId,
7676
                $preRequisiteId,
7677
                $course_id,
7678
                $selectedMaxScore,
7679
                $selectedMinScore,
7680
                $displayOrder,
7681
                $lpItemRepo,
7682
                $em
7683
            ) {
7684
                $mainUrl = '';
7685
                $fullTitle = $item['title'];
7686
                $title = cut($fullTitle, self::MAX_LP_ITEM_TITLE_LENGTH);
7687
                $itemId = $item['iid'];
7688
                $type = $item['itemType'];
7689
                $lpId = $this->get_id();
7690
                $iconName = str_replace(' ', '', $type);
7691
                $icon = Display::return_icon(
7692
                    'lp_'.$iconName.'.png',
7693
                    '',
7694
                    [],
7695
                    ICON_SIZE_TINY
7696
                );
7697
                $url = $mainUrl.'&view=build&id='.$itemId.'&lp_id='.$lpId;
7698
7699
                if ($itemId == $currentItemId) {
7700
                    return '';
7701
                }
7702
7703
                if ($displayOrder < $item['displayOrder']) {
7704
                    return '';
7705
                }
7706
7707
                $selectedMaxScoreValue = isset($selectedMaxScore[$itemId]) ? $selectedMaxScore[$itemId] : $item['maxScore'];
7708
                $selectedMinScoreValue = $selectedMinScore[$itemId] ?? 0;
7709
                $masteryScoreAsMinValue = $masteryScore[$itemId] ?? 0;
7710
7711
                $return = '<tr>';
7712
                $return .= '<td '.((TOOL_QUIZ != $type && TOOL_HOTPOTATOES != $type) ? ' colspan="3"' : '').'>';
7713
                $return .= '<div style="margin-left:'.($item['lvl'] * 20).'px;" class="radio learnpath">';
7714
                $return .= '<label for="id'.$itemId.'">';
7715
7716
                $checked = '';
7717
                if (null !== $preRequisiteId) {
7718
                    $checked = in_array($preRequisiteId, [$itemId, $item['ref']]) ? ' checked="checked" ' : '';
7719
                }
7720
7721
                $disabled = 'dir' === $type ? ' disabled="disabled" ' : '';
7722
7723
                $return .= '<input
7724
                    '.$checked.' '.$disabled.'
7725
                    id="id'.$itemId.'"
7726
                    name="prerequisites"
7727
                    type="radio"
7728
                    value="'.$itemId.'" />';
7729
7730
                $return .= $icon.'&nbsp;&nbsp;'.$item['title'].'</label>';
7731
                $return .= '</div>';
7732
                $return .= '</td>';
7733
7734
                if (TOOL_QUIZ == $type) {
7735
                    // lets update max_score Tests information depending of the Tests Advanced properties
7736
                    $exercise = new Exercise($course_id);
7737
                    /** @var CLpItem $itemEntity */
7738
                    $itemEntity = $lpItemRepo->find($itemId);
7739
                    $exercise->read($item['path']);
7740
                    $itemEntity->setMaxScore($exercise->get_max_score());
7741
                    $em->persist($itemEntity);
7742
                    $em->flush($itemEntity);
7743
7744
                    $item['maxScore'] = $exercise->get_max_score();
7745
7746
                    if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
7747
                        // Backwards compatibility with 1.9.x use mastery_score as min value
7748
                        $selectedMinScoreValue = $masteryScoreAsMinValue;
7749
                    }
7750
                    $return .= '<td>';
7751
                    $return .= '<input
7752
                        class="form-control"
7753
                        size="4" maxlength="3"
7754
                        name="min_'.$itemId.'"
7755
                        type="number"
7756
                        min="0"
7757
                        step="any"
7758
                        max="'.$item['maxScore'].'"
7759
                        value="'.$selectedMinScoreValue.'"
7760
                    />';
7761
                    $return .= '</td>';
7762
                    $return .= '<td>';
7763
                    $return .= '<input
7764
                        class="form-control"
7765
                        size="4"
7766
                        maxlength="3"
7767
                        name="max_'.$itemId.'"
7768
                        type="number"
7769
                        min="0"
7770
                        step="any"
7771
                        max="'.$item['maxScore'].'"
7772
                        value="'.$selectedMaxScoreValue.'"
7773
                    />';
7774
                        $return .= '</td>';
7775
                    }
7776
7777
                if (TOOL_HOTPOTATOES == $type) {
7778
                    $return .= '<td>';
7779
                    $return .= '<input
7780
                        size="4"
7781
                        maxlength="3"
7782
                        name="min_'.$itemId.'"
7783
                        type="number"
7784
                        min="0"
7785
                        step="any"
7786
                        max="'.$item['maxScore'].'"
7787
                        value="'.$selectedMinScoreValue.'"
7788
                    />';
7789
                        $return .= '</td>';
7790
                        $return .= '<td>';
7791
                        $return .= '<input
7792
                        size="4"
7793
                        maxlength="3"
7794
                        name="max_'.$itemId.'"
7795
                        type="number"
7796
                        min="0"
7797
                        step="any"
7798
                        max="'.$item['maxScore'].'"
7799
                        value="'.$selectedMaxScoreValue.'"
7800
                    />';
7801
                    $return .= '</td>';
7802
                }
7803
                $return .= '</tr>';
7804
7805
                return $return;
7806
            },
7807
        ];
7808
7809
        $tree = $lpItemRepo->childrenHierarchy($itemRoot, false, $options);
7810
        $return .= $tree;
7811
        $return .= '</tbody>';
7812
        $return .= '</table>';
7813
        $return .= '</div>';
7814
        $return .= '<div class="form-group">';
7815
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
7816
            get_lang('Save prerequisites settings').'</button>';
7817
        $return .= '</form>';
7818
7819
        return $return;
7820
    }
7821
7822
    /**
7823
     * Return HTML list to allow prerequisites selection for lp.
7824
     */
7825
    public function display_lp_prerequisites_list(FormValidator $form)
7826
    {
7827
        $lp_id = $this->lp_id;
7828
        $prerequisiteId = $this->entity->getPrerequisite();
7829
7830
        $repo = Container::getLpRepository();
7831
        $qb = $repo->findAllByCourse(api_get_course_entity(), api_get_session_entity());
7832
        /** @var CLp[] $lps */
7833
        $lps = $qb->getQuery()->getResult();
7834
7835
        //$session_id = api_get_session_id();
7836
        /*$session_condition = api_get_session_condition($session_id, true, true);
7837
        $sql = "SELECT * FROM $tbl_lp
7838
                WHERE c_id = $course_id $session_condition
7839
                ORDER BY display_order ";
7840
        $rs = Database::query($sql);*/
7841
7842
        $items = [get_lang('none')];
7843
        foreach ($lps as $lp) {
7844
            $myLpId = $lp->getIid();
7845
            if ($myLpId == $lp_id) {
7846
                continue;
7847
            }
7848
            $items[$myLpId] = $lp->getName();
7849
            /*$return .= '<option
7850
                value="'.$myLpId.'" '.(($myLpId == $prerequisiteId) ? ' selected ' : '').'>'.
7851
                $lp->getName().
7852
                '</option>';*/
7853
        }
7854
7855
        $select = $form->addSelect('prerequisites', get_lang('Prerequisites'), $items);
7856
        $select->setSelected($prerequisiteId);
7857
    }
7858
7859
    /**
7860
     * Creates a list with all the documents in it.
7861
     *
7862
     * @param bool $showInvisibleFiles
7863
     *
7864
     * @throws Exception
7865
     *
7866
     *
7867
     * @return string
7868
     */
7869
    public function get_documents($showInvisibleFiles = false)
7870
    {
7871
        $course_info = api_get_course_info();
7872
        $sessionId = api_get_session_id();
7873
        $documentTree = DocumentManager::get_document_preview(
7874
            $course_info,
7875
            $this->lp_id,
7876
            null,
7877
            $sessionId,
7878
            true,
7879
            null,
7880
            null,
7881
            $showInvisibleFiles,
7882
            true
7883
        );
7884
7885
        $form = new FormValidator(
7886
            'form_upload',
7887
            'POST',
7888
            $this->getCurrentBuildingModeURL(),
7889
            '',
7890
            ['enctype' => 'multipart/form-data']
7891
        );
7892
7893
        $folders = DocumentManager::get_all_document_folders(
7894
            api_get_course_info(),
7895
            0,
7896
            true
7897
        );
7898
7899
        $folder = $this->generate_lp_folder(api_get_course_info());
7900
7901
        DocumentManager::build_directory_selector(
7902
            $folders,
7903
            $folder->getIid(),
7904
            [],
7905
            true,
7906
            $form,
7907
            'directory_parent_id'
7908
        );
7909
7910
        $group = [
7911
            $form->createElement(
7912
                'radio',
7913
                'if_exists',
7914
                get_lang('If file exists:'),
7915
                get_lang('Do nothing'),
7916
                'nothing'
7917
            ),
7918
            $form->createElement(
7919
                'radio',
7920
                'if_exists',
7921
                null,
7922
                get_lang('Overwrite the existing file'),
7923
                'overwrite'
7924
            ),
7925
            $form->createElement(
7926
                'radio',
7927
                'if_exists',
7928
                null,
7929
                get_lang('Rename the uploaded file if it exists'),
7930
                'rename'
7931
            ),
7932
        ];
7933
        $form->addGroup($group, null, get_lang('If file exists:'));
7934
7935
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
7936
        $defaultFileExistsOption = 'rename';
7937
        if (!empty($fileExistsOption)) {
7938
            $defaultFileExistsOption = $fileExistsOption;
7939
        }
7940
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
7941
7942
        // Check box options
7943
        $form->addElement(
7944
            'checkbox',
7945
            'unzip',
7946
            get_lang('Options'),
7947
            get_lang('Uncompress zip')
7948
        );
7949
7950
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
7951
        $form->addMultipleUpload($url);
7952
7953
        $lpItem = new CLpItem();
7954
        $lpItem->setTitle('');
7955
        $lpItem->setItemType(TOOL_DOCUMENT);
7956
        $new = $this->displayDocumentForm('add', $lpItem);
7957
7958
        /*$lpItem = new CLpItem();
7959
        $lpItem->setItemType(TOOL_READOUT_TEXT);
7960
        $frmReadOutText = $this->displayDocumentForm('add');*/
7961
7962
        $headers = [
7963
            get_lang('Files'),
7964
            get_lang('Create a new document'),
7965
            //get_lang('Create read-out text'),
7966
            get_lang('Upload'),
7967
        ];
7968
7969
        return Display::tabs(
7970
            $headers,
7971
            [$documentTree, $new, $form->returnForm()],
7972
            'subtab'
7973
        );
7974
    }
7975
7976
    /**
7977
     * Creates a list with all the exercises (quiz) in it.
7978
     *
7979
     * @return string
7980
     */
7981
    public function get_exercises()
7982
    {
7983
        $course_id = api_get_course_int_id();
7984
        $session_id = api_get_session_id();
7985
        $userInfo = api_get_user_info();
7986
7987
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7988
        $condition_session = api_get_session_condition($session_id, true, true);
7989
        $setting = 'true' === api_get_setting('lp.show_invisible_exercise_in_lp_toc');
7990
7991
        //$activeCondition = ' active <> -1 ';
7992
        $active = 2;
7993
        if ($setting) {
7994
            $active = 1;
7995
            //$activeCondition = ' active = 1 ';
7996
        }
7997
7998
        $categoryCondition = '';
7999
8000
        $keyword = $_REQUEST['keyword'] ?? null;
8001
        $categoryId = $_REQUEST['category_id'] ?? null;
8002
        /*if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
8003
            $categoryCondition = " AND exercise_category_id = $categoryId ";
8004
        }
8005
8006
        $keywordCondition = '';
8007
8008
        if (!empty($keyword)) {
8009
            $keyword = Database::escape_string($keyword);
8010
            $keywordCondition = " AND title LIKE '%$keyword%' ";
8011
        }
8012
        */
8013
        $course = api_get_course_entity($course_id);
8014
        $session = api_get_session_entity($session_id);
8015
8016
        $qb = Container::getQuizRepository()->findAllByCourse($course, $session, $keyword, $active, false, $categoryId);
8017
        /** @var CQuiz[] $exercises */
8018
        $exercises = $qb->getQuery()->getResult();
8019
8020
        /*$sql_quiz = "SELECT * FROM $tbl_quiz
8021
                     WHERE
8022
                            c_id = $course_id AND
8023
                            $activeCondition
8024
                            $condition_session
8025
                            $categoryCondition
8026
                            $keywordCondition
8027
                     ORDER BY title ASC";
8028
        $res_quiz = Database::query($sql_quiz);*/
8029
8030
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
8031
8032
        // Create a search-box
8033
        /*$form = new FormValidator('search_simple', 'get', $currentUrl);
8034
        $form->addHidden('action', 'add_item');
8035
        $form->addHidden('type', 'step');
8036
        $form->addHidden('lp_id', $this->lp_id);
8037
        $form->addHidden('lp_build_selected', '2');
8038
8039
        $form->addCourseHiddenParams();
8040
        $form->addText(
8041
            'keyword',
8042
            get_lang('Search'),
8043
            false,
8044
            [
8045
                'aria-label' => get_lang('Search'),
8046
            ]
8047
        );
8048
8049
        if (api_get_configuration_value('allow_exercise_categories')) {
8050
            $manager = new ExerciseCategoryManager();
8051
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
8052
            if (!empty($options)) {
8053
                $form->addSelect(
8054
                    'category_id',
8055
                    get_lang('Category'),
8056
                    $options,
8057
                    ['placeholder' => get_lang('Please select an option')]
8058
                );
8059
            }
8060
        }
8061
8062
        $form->addButtonSearch(get_lang('Search'));
8063
        $return = $form->returnForm();*/
8064
8065
        $return = '<ul class = "list-group lp_resource">';
8066
        $return .= '<li class="list-group-item lp_resource_element disable_drag">';
8067
        $return .= Display::return_icon('new_exercice.png');
8068
        $return .= '<a
8069
            href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
8070
            get_lang('New test').'</a>';
8071
        $return .= '</li>';
8072
8073
        $previewIcon = Display::return_icon(
8074
            'preview_view.png',
8075
            get_lang('Preview')
8076
        );
8077
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
8078
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8079
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
8080
        foreach ($exercises as $exercise) {
8081
            $exerciseId = $exercise->getIid();
8082
            $title = strip_tags(api_html_entity_decode($exercise->getTitle()));
8083
            $visibility = $exercise->isVisible($course, $session);
8084
8085
            $link = Display::url(
8086
                $previewIcon,
8087
                $exerciseUrl.'&exerciseId='.$exerciseId,
8088
                ['target' => '_blank']
8089
            );
8090
            $return .= '<li
8091
                class="list-group-item lp_resource_element"
8092
                id="'.$exerciseId.'"
8093
                data-id="'.$exerciseId.'"
8094
                title="'.$title.'">';
8095
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
8096
            $return .= $quizIcon;
8097
            $sessionStar = '';
8098
            /*$sessionStar = api_get_session_image(
8099
                $row_quiz['session_id'],
8100
                $userInfo['status']
8101
            );*/
8102
            $return .= Display::url(
8103
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
8104
                api_get_self().'?'.
8105
                    api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$exerciseId.'&lp_id='.$this->lp_id,
8106
                [
8107
                    'class' => false === $visibility ? 'moved text-muted ' : 'moved link_with_id',
8108
                    'data_type' => 'quiz',
8109
                    'data-id' => $exerciseId,
8110
                ]
8111
            );
8112
            $return .= '</li>';
8113
        }
8114
8115
        $return .= '</ul>';
8116
8117
        return $return;
8118
    }
8119
8120
    /**
8121
     * Creates a list with all the links in it.
8122
     *
8123
     * @return string
8124
     */
8125
    public function get_links()
8126
    {
8127
        $sessionId = api_get_session_id();
8128
        $repo = Container::getLinkRepository();
8129
8130
        $course = api_get_course_entity();
8131
        $session = api_get_session_entity($sessionId);
8132
        $qb = $repo->getResourcesByCourse($course, $session);
8133
        /** @var CLink[] $links */
8134
        $links = $qb->getQuery()->getResult();
8135
8136
        $selfUrl = api_get_self();
8137
        $courseIdReq = api_get_cidreq();
8138
        $userInfo = api_get_user_info();
8139
8140
        $moveEverywhereIcon = Display::return_icon(
8141
            'move_everywhere.png',
8142
            get_lang('Move'),
8143
            [],
8144
            ICON_SIZE_TINY
8145
        );
8146
8147
        /*$condition_session = api_get_session_condition(
8148
            $session_id,
8149
            true,
8150
            true,
8151
            'link.session_id'
8152
        );
8153
8154
        $sql = "SELECT
8155
                    link.id as link_id,
8156
                    link.title as link_title,
8157
                    link.session_id as link_session_id,
8158
                    link.category_id as category_id,
8159
                    link_category.category_title as category_title
8160
                FROM $tbl_link as link
8161
                LEFT JOIN $linkCategoryTable as link_category
8162
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
8163
                WHERE link.c_id = $course_id $condition_session
8164
                ORDER BY link_category.category_title ASC, link.title ASC";
8165
        $result = Database::query($sql);*/
8166
        $categorizedLinks = [];
8167
        $categories = [];
8168
8169
        foreach ($links as $link) {
8170
            $categoryId = null !== $link->getCategory() ? $link->getCategory()->getIid() : 0;
8171
            if (empty($categoryId)) {
8172
                $categories[0] = get_lang('Uncategorized');
8173
            } else {
8174
                $category = $link->getCategory();
8175
                $categories[$categoryId] = $category->getCategoryTitle();
8176
            }
8177
            $categorizedLinks[$categoryId][$link->getIid()] = $link;
8178
        }
8179
8180
        $linksHtmlCode =
8181
            '<script>
8182
            function toggle_tool(tool, id) {
8183
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
8184
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
8185
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
8186
                } else {
8187
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
8188
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
8189
                }
8190
            }
8191
        </script>
8192
8193
        <ul class="list-group lp_resource">
8194
            <li class="list-group-item lp_resource_element disable_drag ">
8195
                '.Display::return_icon('linksnew.gif').'
8196
                <a
8197
                href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'"
8198
                title="'.get_lang('Add a link').'">'.
8199
                get_lang('Add a link').'
8200
                </a>
8201
            </li>';
8202
        $linkIcon = Display::return_icon('links.png', '', [], ICON_SIZE_TINY);
8203
        foreach ($categorizedLinks as $categoryId => $links) {
8204
            $linkNodes = null;
8205
            /** @var CLink $link */
8206
            foreach ($links as $key => $link) {
8207
                $title = $link->getTitle();
8208
                $id = $link->getIid();
8209
                $linkUrl = Display::url(
8210
                    Display::return_icon('preview_view.png', get_lang('Preview')),
8211
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
8212
                    ['target' => '_blank']
8213
                );
8214
8215
                if ($link->isVisible($course, $session)) {
8216
                    //$sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
8217
                    $sessionStar = '';
8218
                    $url = $selfUrl.'?'.$courseIdReq.'&action=add_item&type='.TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id;
8219
                    $link = Display::url(
8220
                        Security::remove_XSS($title).$sessionStar.$linkUrl,
8221
                        $url,
8222
                        [
8223
                            'class' => 'moved link_with_id',
8224
                            'data-id' => $key,
8225
                            'data_type' => TOOL_LINK,
8226
                            'title' => $title,
8227
                        ]
8228
                    );
8229
                    $linkNodes .=
8230
                        "<li
8231
                            class='list-group-item lp_resource_element'
8232
                            id= $id
8233
                            data-id= $id
8234
                            >
8235
                         <a class='moved' href='#'>
8236
                            $moveEverywhereIcon
8237
                        </a>
8238
                        $linkIcon $link
8239
                        </li>";
8240
                }
8241
            }
8242
            $linksHtmlCode .=
8243
                '<li class="list-group-item disable_drag">
8244
                    <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" >
8245
                        <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
8246
                        align="absbottom" />
8247
                    </a>
8248
                    <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
8249
                </li>
8250
            '.
8251
                $linkNodes.
8252
            '';
8253
            //<div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.
8254
        }
8255
        $linksHtmlCode .= '</ul>';
8256
8257
        return $linksHtmlCode;
8258
    }
8259
8260
    /**
8261
     * Creates a list with all the student publications in it.
8262
     *
8263
     * @return string
8264
     */
8265
    public function get_student_publications()
8266
    {
8267
        $return = '<ul class="list-group lp_resource">';
8268
        $return .= '<li class="list-group-item lp_resource_element">';
8269
        /*
8270
        $return .= Display::return_icon('works_new.gif');
8271
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
8272
            get_lang('Add the Assignments tool to the course').'</a>';
8273
        $return .= '</li>';*/
8274
8275
        $works = getWorkListTeacher(0, 100, null, null, null);
8276
        if (!empty($works)) {
8277
            $icon = Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
8278
            foreach ($works as $work) {
8279
                $workId = $work['iid'];
8280
                $link = Display::url(
8281
                    Display::return_icon('preview_view.png', get_lang('Preview')),
8282
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$workId,
8283
                    ['target' => '_blank']
8284
                );
8285
8286
                $return .= '<li
8287
                    class="list-group-item lp_resource_element"
8288
                    id="'.$workId.'"
8289
                    data-id="'.$workId.'"
8290
                    >';
8291
                $return .= '<a class="moved" href="#">';
8292
                $return .= Display::return_icon(
8293
                    'move_everywhere.png',
8294
                    get_lang('Move'),
8295
                    [],
8296
                    ICON_SIZE_TINY
8297
                );
8298
                $return .= '</a> ';
8299
8300
                $return .= $icon;
8301
                $return .= Display::url(
8302
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link,
8303
                    api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&file='.$work['iid'].'&lp_id='.$this->lp_id,
8304
                    [
8305
                        'class' => 'moved link_with_id',
8306
                        'data-id' => $work['iid'],
8307
                        'data_type' => TOOL_STUDENTPUBLICATION,
8308
                        'title' => Security::remove_XSS(cut(strip_tags($work['title']), 80)),
8309
                    ]
8310
                );
8311
                $return .= '</li>';
8312
            }
8313
        }
8314
8315
        $return .= '</ul>';
8316
8317
        return $return;
8318
    }
8319
8320
    /**
8321
     * Creates a list with all the forums in it.
8322
     *
8323
     * @return string
8324
     */
8325
    public function get_forums()
8326
    {
8327
        $forumCategories = get_forum_categories();
8328
        $forumsInNoCategory = get_forums_in_category(0);
8329
        if (!empty($forumsInNoCategory)) {
8330
            $forumCategories = array_merge(
8331
                $forumCategories,
8332
                [
8333
                    [
8334
                        'cat_id' => 0,
8335
                        'session_id' => 0,
8336
                        'visibility' => 1,
8337
                        'cat_comment' => null,
8338
                    ],
8339
                ]
8340
            );
8341
        }
8342
8343
        $a_forums = [];
8344
        $courseEntity = api_get_course_entity(api_get_course_int_id());
8345
        $sessionEntity = api_get_session_entity(api_get_session_id());
8346
8347
        foreach ($forumCategories as $forumCategory) {
8348
            // The forums in this category.
8349
            $forumsInCategory = get_forums_in_category($forumCategory->getIid());
8350
            if (!empty($forumsInCategory)) {
8351
                foreach ($forumsInCategory as $forum) {
8352
                    if ($forum->isVisible($courseEntity, $sessionEntity)) {
8353
                        $a_forums[] = $forum;
8354
                    }
8355
                }
8356
            }
8357
        }
8358
8359
        $return = '<ul class="list-group lp_resource">';
8360
8361
        // First add link
8362
        $return .= '<li class="list-group-item lp_resource_element disable_drag">';
8363
        $return .= Display::return_icon('new_forum.png');
8364
        $return .= Display::url(
8365
            get_lang('Create a new forum'),
8366
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
8367
                'action' => 'add',
8368
                'content' => 'forum',
8369
                'lp_id' => $this->lp_id,
8370
            ]),
8371
            ['title' => get_lang('Create a new forum')]
8372
        );
8373
        $return .= '</li>';
8374
8375
        $return .= '<script>
8376
            function toggle_forum(forum_id) {
8377
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
8378
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
8379
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
8380
                } else {
8381
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
8382
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
8383
                }
8384
            }
8385
        </script>';
8386
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8387
        foreach ($a_forums as $forum) {
8388
            $forumId = $forum->getIid();
8389
            $title = Security::remove_XSS($forum->getForumTitle());
8390
            $link = Display::url(
8391
                Display::return_icon('preview_view.png', get_lang('Preview')),
8392
                api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forumId,
8393
                ['target' => '_blank']
8394
            );
8395
8396
            $return .= '<li
8397
                    class="list-group-item lp_resource_element"
8398
                    id="'.$forumId.'"
8399
                    data-id="'.$forumId.'"
8400
                    >';
8401
            $return .= '<a class="moved" href="#">';
8402
            $return .= $moveIcon;
8403
            $return .= ' </a>';
8404
            $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
8405
8406
            $moveLink = Display::url(
8407
                $title.' '.$link,
8408
                api_get_self().'?'.
8409
                api_get_cidreq().'&action=add_item&type='.TOOL_FORUM.'&forum_id='.$forumId.'&lp_id='.$this->lp_id,
8410
                [
8411
                    'class' => 'moved link_with_id',
8412
                    'data-id' => $forumId,
8413
                    'data_type' => TOOL_FORUM,
8414
                    'title' => $title,
8415
                    'style' => 'vertical-align:middle',
8416
                ]
8417
            );
8418
            $return .= '<a onclick="javascript:toggle_forum('.$forumId.');" style="cursor:hand; vertical-align:middle">
8419
                            <img
8420
                                src="'.Display::returnIconPath('add.png').'"
8421
                                id="forum_'.$forumId.'_opener" align="absbottom"
8422
                             />
8423
                        </a>
8424
                        '.$moveLink;
8425
            $return .= '</li>';
8426
8427
            $return .= '<div style="display:none" id="forum_'.$forumId.'_content">';
8428
            $threads = get_threads($forumId);
8429
            if (is_array($threads)) {
8430
                foreach ($threads as $thread) {
8431
                    $threadId = $thread->getIid();
8432
                    $link = Display::url(
8433
                        Display::return_icon('preview_view.png', get_lang('Preview')),
8434
                        api_get_path(WEB_CODE_PATH).
8435
                        'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forumId.'&thread='.$threadId,
8436
                        ['target' => '_blank']
8437
                    );
8438
8439
                    $return .= '<li
8440
                        class="list-group-item lp_resource_element"
8441
                      id="'.$threadId.'"
8442
                        data-id="'.$threadId.'"
8443
                    >';
8444
                    $return .= '&nbsp;<a class="moved" href="#">';
8445
                    $return .= $moveIcon;
8446
                    $return .= ' </a>';
8447
                    $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
8448
                    $return .= '<a
8449
                        class="moved link_with_id"
8450
                        data-id="'.$threadId.'"
8451
                        data_type="'.TOOL_THREAD.'"
8452
                        title="'.$thread->getThreadTitle().'"
8453
                        href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_THREAD.'&thread_id='.$threadId.'&lp_id='.$this->lp_id.'"
8454
                        >'.
8455
                        Security::remove_XSS($thread->getThreadTitle()).' '.$link.'</a>';
8456
                    $return .= '</li>';
8457
                }
8458
            }
8459
            $return .= '</div>';
8460
        }
8461
        $return .= '</ul>';
8462
8463
        return $return;
8464
    }
8465
8466
    /**
8467
     * // TODO: The output encoding should be equal to the system encoding.
8468
     *
8469
     * Exports the learning path as a SCORM package. This is the main function that
8470
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
8471
     * whole thing and returns the zip.
8472
     *
8473
     * This method needs to be called in PHP5, as it will fail with non-adequate
8474
     * XML package (like the ones for PHP4), and it is *not* a static method, so
8475
     * you need to call it on a learnpath object.
8476
     *
8477
     * @TODO The method might be redefined later on in the scorm class itself to avoid
8478
     * creating a SCORM structure if there is one already. However, if the initial SCORM
8479
     * path has been modified, it should use the generic method here below.
8480
     *
8481
     * @return string Returns the zip package string, or null if error
8482
     */
8483
    public function scormExport()
8484
    {
8485
        api_set_more_memory_and_time_limits();
8486
8487
        $_course = api_get_course_info();
8488
        $course_id = $_course['real_id'];
8489
        // Create the zip handler (this will remain available throughout the method).
8490
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
8491
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
8492
        $temp_dir_short = uniqid('scorm_export', true);
8493
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
8494
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
8495
        $zip_folder = new PclZip($temp_zip_file);
8496
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
8497
        $root_path = $main_path = api_get_path(SYS_PATH);
8498
        $files_cleanup = [];
8499
8500
        // Place to temporarily stash the zip file.
8501
        // create the temp dir if it doesn't exist
8502
        // or do a cleanup before creating the zip file.
8503
        if (!is_dir($temp_zip_dir)) {
8504
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
8505
        } else {
8506
            // Cleanup: Check the temp dir for old files and delete them.
8507
            $handle = opendir($temp_zip_dir);
8508
            while (false !== ($file = readdir($handle))) {
8509
                if ('.' != $file && '..' != $file) {
8510
                    unlink("$temp_zip_dir/$file");
8511
                }
8512
            }
8513
            closedir($handle);
8514
        }
8515
        $zip_files = $zip_files_abs = $zip_files_dist = [];
8516
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
8517
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
8518
        ) {
8519
            // Remove the possible . at the end of the path.
8520
            $dest_path_to_lp = '.' == substr($this->path, -1) ? substr($this->path, 0, -1) : $this->path;
8521
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
8522
            mkdir(
8523
                $dest_path_to_scorm_folder,
8524
                api_get_permissions_for_new_directories(),
8525
                true
8526
            );
8527
            copyr(
8528
                $current_course_path.'/scorm/'.$this->path,
8529
                $dest_path_to_scorm_folder,
8530
                ['imsmanifest'],
8531
                $zip_files
8532
            );
8533
        }
8534
8535
        // Build a dummy imsmanifest structure.
8536
        // Do not add to the zip yet (we still need it).
8537
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
8538
        // Aggregation Model official document, section "2.3 Content Packaging".
8539
        // We are going to build a UTF-8 encoded manifest.
8540
        // Later we will recode it to the desired (and supported) encoding.
8541
        $xmldoc = new DOMDocument('1.0');
8542
        $root = $xmldoc->createElement('manifest');
8543
        $root->setAttribute('identifier', 'SingleCourseManifest');
8544
        $root->setAttribute('version', '1.1');
8545
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
8546
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
8547
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
8548
        $root->setAttribute(
8549
            'xsi:schemaLocation',
8550
            'http://www.imsproject.org/xsd/imscp_rootv1p1p2 imscp_rootv1p1p2.xsd http://www.imsglobal.org/xsd/imsmd_rootv1p2p1 imsmd_rootv1p2p1.xsd http://www.adlnet.org/xsd/adlcp_rootv1p2 adlcp_rootv1p2.xsd'
8551
        );
8552
        // Build mandatory sub-root container elements.
8553
        $metadata = $xmldoc->createElement('metadata');
8554
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
8555
        $metadata->appendChild($md_schema);
8556
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
8557
        $metadata->appendChild($md_schemaversion);
8558
        $root->appendChild($metadata);
8559
8560
        $organizations = $xmldoc->createElement('organizations');
8561
        $resources = $xmldoc->createElement('resources');
8562
8563
        // Build the only organization we will use in building our learnpaths.
8564
        $organizations->setAttribute('default', 'chamilo_scorm_export');
8565
        $organization = $xmldoc->createElement('organization');
8566
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
8567
        // To set the title of the SCORM entity (=organization), we take the name given
8568
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
8569
        // learning path charset) as it is the encoding that defines how it is stored
8570
        // in the database. Then we convert it to HTML entities again as the "&" character
8571
        // alone is not authorized in XML (must be &amp;).
8572
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
8573
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
8574
        $organization->appendChild($org_title);
8575
        $folder_name = 'document';
8576
8577
        // Removes the learning_path/scorm_folder path when exporting see #4841
8578
        $path_to_remove = '';
8579
        $path_to_replace = '';
8580
        $result = $this->generate_lp_folder($_course);
8581
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
8582
            $path_to_remove = 'document'.$result['dir'];
8583
            $path_to_replace = $folder_name.'/';
8584
        }
8585
8586
        // Fixes chamilo scorm exports
8587
        if ('chamilo_scorm_export' === $this->ref) {
8588
            $path_to_remove = 'scorm/'.$this->path.'/document/';
8589
        }
8590
8591
        // For each element, add it to the imsmanifest structure, then add it to the zip.
8592
        $link_updates = [];
8593
        $links_to_create = [];
8594
        foreach ($this->ordered_items as $index => $itemId) {
8595
            /** @var learnpathItem $item */
8596
            $item = $this->items[$itemId];
8597
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
8598
                // Get included documents from this item.
8599
                if ('sco' === $item->type) {
8600
                    $inc_docs = $item->get_resources_from_source(
8601
                        null,
8602
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
8603
                    );
8604
                } else {
8605
                    $inc_docs = $item->get_resources_from_source();
8606
                }
8607
8608
                // Give a child element <item> to the <organization> element.
8609
                $my_item_id = $item->get_id();
8610
                $my_item = $xmldoc->createElement('item');
8611
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
8612
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
8613
                $my_item->setAttribute('isvisible', 'true');
8614
                // Give a child element <title> to the <item> element.
8615
                $my_title = $xmldoc->createElement(
8616
                    'title',
8617
                    htmlspecialchars(
8618
                        api_utf8_encode($item->get_title()),
8619
                        ENT_QUOTES,
8620
                        'UTF-8'
8621
                    )
8622
                );
8623
                $my_item->appendChild($my_title);
8624
                // Give a child element <adlcp:prerequisites> to the <item> element.
8625
                $my_prereqs = $xmldoc->createElement(
8626
                    'adlcp:prerequisites',
8627
                    $this->get_scorm_prereq_string($my_item_id)
8628
                );
8629
                $my_prereqs->setAttribute('type', 'aicc_script');
8630
                $my_item->appendChild($my_prereqs);
8631
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
8632
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
8633
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
8634
                //$xmldoc->createElement('adlcp:timelimitaction','');
8635
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
8636
                //$xmldoc->createElement('adlcp:datafromlms','');
8637
                // Give a child element <adlcp:masteryscore> to the <item> element.
8638
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
8639
                $my_item->appendChild($my_masteryscore);
8640
8641
                // Attach this item to the organization element or hits parent if there is one.
8642
                if (!empty($item->parent) && 0 != $item->parent) {
8643
                    $children = $organization->childNodes;
8644
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
8645
                    if (is_object($possible_parent)) {
8646
                        $possible_parent->appendChild($my_item);
8647
                    } else {
8648
                        if ($this->debug > 0) {
8649
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
8650
                        }
8651
                    }
8652
                } else {
8653
                    if ($this->debug > 0) {
8654
                        error_log('No parent');
8655
                    }
8656
                    $organization->appendChild($my_item);
8657
                }
8658
8659
                // Get the path of the file(s) from the course directory root.
8660
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
8661
                $my_xml_file_path = $my_file_path;
8662
                if (!empty($path_to_remove)) {
8663
                    // From docs
8664
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
8665
8666
                    // From quiz
8667
                    if ('chamilo_scorm_export' === $this->ref) {
8668
                        $path_to_remove = 'scorm/'.$this->path.'/';
8669
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
8670
                    }
8671
                }
8672
8673
                $my_sub_dir = dirname($my_file_path);
8674
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
8675
                $my_xml_sub_dir = $my_sub_dir;
8676
                // Give a <resource> child to the <resources> element
8677
                $my_resource = $xmldoc->createElement('resource');
8678
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
8679
                $my_resource->setAttribute('type', 'webcontent');
8680
                $my_resource->setAttribute('href', $my_xml_file_path);
8681
                // adlcp:scormtype can be either 'sco' or 'asset'.
8682
                if ('sco' === $item->type) {
8683
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
8684
                } else {
8685
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
8686
                }
8687
                // xml:base is the base directory to find the files declared in this resource.
8688
                $my_resource->setAttribute('xml:base', '');
8689
                // Give a <file> child to the <resource> element.
8690
                $my_file = $xmldoc->createElement('file');
8691
                $my_file->setAttribute('href', $my_xml_file_path);
8692
                $my_resource->appendChild($my_file);
8693
8694
                // Dependency to other files - not yet supported.
8695
                $i = 1;
8696
                if ($inc_docs) {
8697
                    foreach ($inc_docs as $doc_info) {
8698
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
8699
                            continue;
8700
                        }
8701
                        $my_dep = $xmldoc->createElement('resource');
8702
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
8703
                        $my_dep->setAttribute('identifier', $res_id);
8704
                        $my_dep->setAttribute('type', 'webcontent');
8705
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
8706
                        $my_dep_file = $xmldoc->createElement('file');
8707
                        // Check type of URL.
8708
                        if ('remote' == $doc_info[1]) {
8709
                            // Remote file. Save url as is.
8710
                            $my_dep_file->setAttribute('href', $doc_info[0]);
8711
                            $my_dep->setAttribute('xml:base', '');
8712
                        } elseif ('local' === $doc_info[1]) {
8713
                            switch ($doc_info[2]) {
8714
                                case 'url':
8715
                                    // Local URL - save path as url for now, don't zip file.
8716
                                    $abs_path = api_get_path(SYS_PATH).
8717
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
8718
                                    $current_dir = dirname($abs_path);
8719
                                    $current_dir = str_replace('\\', '/', $current_dir);
8720
                                    $file_path = realpath($abs_path);
8721
                                    $file_path = str_replace('\\', '/', $file_path);
8722
                                    $my_dep_file->setAttribute('href', $file_path);
8723
                                    $my_dep->setAttribute('xml:base', '');
8724
                                    if (false !== strstr($file_path, $main_path)) {
8725
                                        // The calculated real path is really inside Chamilo's root path.
8726
                                        // Reduce file path to what's under the DocumentRoot.
8727
                                        $replace = $file_path;
8728
                                        $file_path = substr($file_path, strlen($root_path) - 1);
8729
                                        $destinationFile = $file_path;
8730
8731
                                        if (false !== strstr($file_path, 'upload/users')) {
8732
                                            $pos = strpos($file_path, 'my_files/');
8733
                                            if (false !== $pos) {
8734
                                                $onlyDirectory = str_replace(
8735
                                                    'upload/users/',
8736
                                                    '',
8737
                                                    substr($file_path, $pos, strlen($file_path))
8738
                                                );
8739
                                            }
8740
                                            $replace = $onlyDirectory;
8741
                                            $destinationFile = $replace;
8742
                                        }
8743
                                        $zip_files_abs[] = $file_path;
8744
                                        $link_updates[$my_file_path][] = [
8745
                                            'orig' => $doc_info[0],
8746
                                            'dest' => $destinationFile,
8747
                                            'replace' => $replace,
8748
                                        ];
8749
                                        $my_dep_file->setAttribute('href', $file_path);
8750
                                        $my_dep->setAttribute('xml:base', '');
8751
                                    } elseif (empty($file_path)) {
8752
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
8753
                                        $file_path = str_replace('//', '/', $file_path);
8754
                                        if (file_exists($file_path)) {
8755
                                            // We get the relative path.
8756
                                            $file_path = substr($file_path, strlen($current_dir));
8757
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
8758
                                            $link_updates[$my_file_path][] = [
8759
                                                'orig' => $doc_info[0],
8760
                                                'dest' => $file_path,
8761
                                            ];
8762
                                            $my_dep_file->setAttribute('href', $file_path);
8763
                                            $my_dep->setAttribute('xml:base', '');
8764
                                        }
8765
                                    }
8766
                                    break;
8767
                                case 'abs':
8768
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
8769
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
8770
                                    $my_dep->setAttribute('xml:base', '');
8771
8772
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
8773
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
8774
                                    $abs_img_path_without_subdir = $doc_info[0];
8775
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
8776
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
8777
                                    if (0 === $pos) {
8778
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
8779
                                    }
8780
8781
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
8782
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
8783
8784
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
8785
                                    $cur_path = '/' == substr($current_course_path, -1) ? $current_course_path : $current_course_path.'/';
8786
                                    // Check if the current document is in that path.
8787
                                    if (false !== strstr($file_path, $cur_path)) {
8788
                                        $destinationFile = substr($file_path, strlen($cur_path));
8789
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
8790
8791
                                        $fileToTest = $cur_path.$my_file_path;
8792
                                        if (!empty($path_to_remove)) {
8793
                                            $fileToTest = str_replace(
8794
                                                $path_to_remove.'/',
8795
                                                $path_to_replace,
8796
                                                $cur_path.$my_file_path
8797
                                            );
8798
                                        }
8799
8800
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
8801
8802
                                        // Put the current document in the zip (this array is the array
8803
                                        // that will manage documents already in the course folder - relative).
8804
                                        $zip_files[] = $filePathNoCoursePart;
8805
                                        // Update the links to the current document in the
8806
                                        // containing document (make them relative).
8807
                                        $link_updates[$my_file_path][] = [
8808
                                            'orig' => $doc_info[0],
8809
                                            'dest' => $destinationFile,
8810
                                            'replace' => $relative_path,
8811
                                        ];
8812
8813
                                        $my_dep_file->setAttribute('href', $file_path);
8814
                                        $my_dep->setAttribute('xml:base', '');
8815
                                    } elseif (false !== strstr($file_path, $main_path)) {
8816
                                        // The calculated real path is really inside Chamilo's root path.
8817
                                        // Reduce file path to what's under the DocumentRoot.
8818
                                        $file_path = substr($file_path, strlen($root_path));
8819
                                        $zip_files_abs[] = $file_path;
8820
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
8821
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
8822
                                        $my_dep->setAttribute('xml:base', '');
8823
                                    } elseif (empty($file_path)) {
8824
                                        // Probably this is an image inside "/main" directory
8825
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
8826
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
8827
8828
                                        if (file_exists($file_path)) {
8829
                                            if (false !== strstr($file_path, 'main/default_course_document')) {
8830
                                                // We get the relative path.
8831
                                                $pos = strpos($file_path, 'main/default_course_document/');
8832
                                                if (false !== $pos) {
8833
                                                    $onlyDirectory = str_replace(
8834
                                                        'main/default_course_document/',
8835
                                                        '',
8836
                                                        substr($file_path, $pos, strlen($file_path))
8837
                                                    );
8838
                                                }
8839
8840
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
8841
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
8842
                                                $zip_files_abs[] = $fileAbs;
8843
                                                $link_updates[$my_file_path][] = [
8844
                                                    'orig' => $doc_info[0],
8845
                                                    'dest' => $destinationFile,
8846
                                                ];
8847
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
8848
                                                $my_dep->setAttribute('xml:base', '');
8849
                                            }
8850
                                        }
8851
                                    }
8852
                                    break;
8853
                                case 'rel':
8854
                                    // Path relative to the current document.
8855
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
8856
                                    if ('..' === substr($doc_info[0], 0, 2)) {
8857
                                        // Relative path going up.
8858
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
8859
                                        $current_dir = str_replace('\\', '/', $current_dir);
8860
                                        $file_path = realpath($current_dir.$doc_info[0]);
8861
                                        $file_path = str_replace('\\', '/', $file_path);
8862
                                        if (false !== strstr($file_path, $main_path)) {
8863
                                            // The calculated real path is really inside Chamilo's root path.
8864
                                            // Reduce file path to what's under the DocumentRoot.
8865
                                            $file_path = substr($file_path, strlen($root_path));
8866
                                            $zip_files_abs[] = $file_path;
8867
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
8868
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
8869
                                            $my_dep->setAttribute('xml:base', '');
8870
                                        }
8871
                                    } else {
8872
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
8873
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
8874
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
8875
                                    }
8876
                                    break;
8877
                                default:
8878
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
8879
                                    $my_dep->setAttribute('xml:base', '');
8880
                                    break;
8881
                            }
8882
                        }
8883
                        $my_dep->appendChild($my_dep_file);
8884
                        $resources->appendChild($my_dep);
8885
                        $dependency = $xmldoc->createElement('dependency');
8886
                        $dependency->setAttribute('identifierref', $res_id);
8887
                        $my_resource->appendChild($dependency);
8888
                        $i++;
8889
                    }
8890
                }
8891
                $resources->appendChild($my_resource);
8892
                $zip_files[] = $my_file_path;
8893
            } else {
8894
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
8895
                switch ($item->type) {
8896
                    case TOOL_LINK:
8897
                        $my_item = $xmldoc->createElement('item');
8898
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
8899
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
8900
                        $my_item->setAttribute('isvisible', 'true');
8901
                        // Give a child element <title> to the <item> element.
8902
                        $my_title = $xmldoc->createElement(
8903
                            'title',
8904
                            htmlspecialchars(
8905
                                api_utf8_encode($item->get_title()),
8906
                                ENT_QUOTES,
8907
                                'UTF-8'
8908
                            )
8909
                        );
8910
                        $my_item->appendChild($my_title);
8911
                        // Give a child element <adlcp:prerequisites> to the <item> element.
8912
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
8913
                        $my_prereqs->setAttribute('type', 'aicc_script');
8914
                        $my_item->appendChild($my_prereqs);
8915
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
8916
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
8917
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
8918
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
8919
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
8920
                        //$xmldoc->createElement('adlcp:datafromlms', '');
8921
                        // Give a child element <adlcp:masteryscore> to the <item> element.
8922
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
8923
                        $my_item->appendChild($my_masteryscore);
8924
8925
                        // Attach this item to the organization element or its parent if there is one.
8926
                        if (!empty($item->parent) && 0 != $item->parent) {
8927
                            $children = $organization->childNodes;
8928
                            for ($i = 0; $i < $children->length; $i++) {
8929
                                $item_temp = $children->item($i);
8930
                                if ('item' == $item_temp->nodeName) {
8931
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
8932
                                        $item_temp->appendChild($my_item);
8933
                                    }
8934
                                }
8935
                            }
8936
                        } else {
8937
                            $organization->appendChild($my_item);
8938
                        }
8939
8940
                        $my_file_path = 'link_'.$item->get_id().'.html';
8941
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
8942
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
8943
                        $rs = Database::query($sql);
8944
                        if ($link = Database::fetch_array($rs)) {
8945
                            $url = $link['url'];
8946
                            $title = stripslashes($link['title']);
8947
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
8948
                            $my_xml_file_path = $my_file_path;
8949
                            $my_sub_dir = dirname($my_file_path);
8950
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
8951
                            $my_xml_sub_dir = $my_sub_dir;
8952
                            // Give a <resource> child to the <resources> element.
8953
                            $my_resource = $xmldoc->createElement('resource');
8954
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
8955
                            $my_resource->setAttribute('type', 'webcontent');
8956
                            $my_resource->setAttribute('href', $my_xml_file_path);
8957
                            // adlcp:scormtype can be either 'sco' or 'asset'.
8958
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
8959
                            // xml:base is the base directory to find the files declared in this resource.
8960
                            $my_resource->setAttribute('xml:base', '');
8961
                            // give a <file> child to the <resource> element.
8962
                            $my_file = $xmldoc->createElement('file');
8963
                            $my_file->setAttribute('href', $my_xml_file_path);
8964
                            $my_resource->appendChild($my_file);
8965
                            $resources->appendChild($my_resource);
8966
                        }
8967
                        break;
8968
                    case TOOL_QUIZ:
8969
                        $exe_id = $item->path;
8970
                        // Should be using ref when everything will be cleaned up in this regard.
8971
                        $exe = new Exercise();
8972
                        $exe->read($exe_id);
8973
                        $my_item = $xmldoc->createElement('item');
8974
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
8975
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
8976
                        $my_item->setAttribute('isvisible', 'true');
8977
                        // Give a child element <title> to the <item> element.
8978
                        $my_title = $xmldoc->createElement(
8979
                            'title',
8980
                            htmlspecialchars(
8981
                                api_utf8_encode($item->get_title()),
8982
                                ENT_QUOTES,
8983
                                'UTF-8'
8984
                            )
8985
                        );
8986
                        $my_item->appendChild($my_title);
8987
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
8988
                        $my_item->appendChild($my_max_score);
8989
                        // Give a child element <adlcp:prerequisites> to the <item> element.
8990
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
8991
                        $my_prereqs->setAttribute('type', 'aicc_script');
8992
                        $my_item->appendChild($my_prereqs);
8993
                        // Give a child element <adlcp:masteryscore> to the <item> element.
8994
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
8995
                        $my_item->appendChild($my_masteryscore);
8996
8997
                        // Attach this item to the organization element or hits parent if there is one.
8998
                        if (!empty($item->parent) && 0 != $item->parent) {
8999
                            $children = $organization->childNodes;
9000
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
9001
                            if ($possible_parent) {
9002
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
9003
                                    $possible_parent->appendChild($my_item);
9004
                                }
9005
                            }
9006
                        } else {
9007
                            $organization->appendChild($my_item);
9008
                        }
9009
9010
                        // Get the path of the file(s) from the course directory root
9011
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
9012
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
9013
                        // Write the contents of the exported exercise into a (big) html file
9014
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
9015
                        $scormExercise = new ScormExercise($exe, true);
9016
                        $contents = $scormExercise->export();
9017
9018
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
9019
                        $res = file_put_contents($tmp_file_path, $contents);
9020
                        if (false === $res) {
9021
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
9022
                        }
9023
                        $files_cleanup[] = $tmp_file_path;
9024
                        $my_xml_file_path = $my_file_path;
9025
                        $my_sub_dir = dirname($my_file_path);
9026
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
9027
                        $my_xml_sub_dir = $my_sub_dir;
9028
                        // Give a <resource> child to the <resources> element.
9029
                        $my_resource = $xmldoc->createElement('resource');
9030
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
9031
                        $my_resource->setAttribute('type', 'webcontent');
9032
                        $my_resource->setAttribute('href', $my_xml_file_path);
9033
                        // adlcp:scormtype can be either 'sco' or 'asset'.
9034
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
9035
                        // xml:base is the base directory to find the files declared in this resource.
9036
                        $my_resource->setAttribute('xml:base', '');
9037
                        // Give a <file> child to the <resource> element.
9038
                        $my_file = $xmldoc->createElement('file');
9039
                        $my_file->setAttribute('href', $my_xml_file_path);
9040
                        $my_resource->appendChild($my_file);
9041
9042
                        // Get included docs.
9043
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
9044
9045
                        // Dependency to other files - not yet supported.
9046
                        $i = 1;
9047
                        foreach ($inc_docs as $doc_info) {
9048
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
9049
                                continue;
9050
                            }
9051
                            $my_dep = $xmldoc->createElement('resource');
9052
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
9053
                            $my_dep->setAttribute('identifier', $res_id);
9054
                            $my_dep->setAttribute('type', 'webcontent');
9055
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
9056
                            $my_dep_file = $xmldoc->createElement('file');
9057
                            // Check type of URL.
9058
                            if ('remote' == $doc_info[1]) {
9059
                                // Remote file. Save url as is.
9060
                                $my_dep_file->setAttribute('href', $doc_info[0]);
9061
                                $my_dep->setAttribute('xml:base', '');
9062
                            } elseif ('local' == $doc_info[1]) {
9063
                                switch ($doc_info[2]) {
9064
                                    case 'url': // Local URL - save path as url for now, don't zip file.
9065
                                        // Save file but as local file (retrieve from URL).
9066
                                        $abs_path = api_get_path(SYS_PATH).
9067
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
9068
                                        $current_dir = dirname($abs_path);
9069
                                        $current_dir = str_replace('\\', '/', $current_dir);
9070
                                        $file_path = realpath($abs_path);
9071
                                        $file_path = str_replace('\\', '/', $file_path);
9072
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
9073
                                        $my_dep->setAttribute('xml:base', '');
9074
                                        if (false !== strstr($file_path, $main_path)) {
9075
                                            // The calculated real path is really inside the chamilo root path.
9076
                                            // Reduce file path to what's under the DocumentRoot.
9077
                                            $file_path = substr($file_path, strlen($root_path));
9078
                                            $zip_files_abs[] = $file_path;
9079
                                            $link_updates[$my_file_path][] = [
9080
                                                'orig' => $doc_info[0],
9081
                                                'dest' => 'document/'.$file_path,
9082
                                            ];
9083
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
9084
                                            $my_dep->setAttribute('xml:base', '');
9085
                                        } elseif (empty($file_path)) {
9086
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
9087
                                            $file_path = str_replace('//', '/', $file_path);
9088
                                            if (file_exists($file_path)) {
9089
                                                $file_path = substr($file_path, strlen($current_dir));
9090
                                                // We get the relative path.
9091
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
9092
                                                $link_updates[$my_file_path][] = [
9093
                                                    'orig' => $doc_info[0],
9094
                                                    'dest' => 'document/'.$file_path,
9095
                                                ];
9096
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9097
                                                $my_dep->setAttribute('xml:base', '');
9098
                                            }
9099
                                        }
9100
                                        break;
9101
                                    case 'abs':
9102
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
9103
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
9104
                                        $current_dir = str_replace('\\', '/', $current_dir);
9105
                                        $file_path = realpath($doc_info[0]);
9106
                                        $file_path = str_replace('\\', '/', $file_path);
9107
                                        $my_dep_file->setAttribute('href', $file_path);
9108
                                        $my_dep->setAttribute('xml:base', '');
9109
9110
                                        if (false !== strstr($file_path, $main_path)) {
9111
                                            // The calculated real path is really inside the chamilo root path.
9112
                                            // Reduce file path to what's under the DocumentRoot.
9113
                                            $file_path = substr($file_path, strlen($root_path));
9114
                                            $zip_files_abs[] = $file_path;
9115
                                            $link_updates[$my_file_path][] = [
9116
                                                'orig' => $doc_info[0],
9117
                                                'dest' => $file_path,
9118
                                            ];
9119
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
9120
                                            $my_dep->setAttribute('xml:base', '');
9121
                                        } elseif (empty($file_path)) {
9122
                                            $docSysPartPath = str_replace(
9123
                                                api_get_path(REL_COURSE_PATH),
9124
                                                '',
9125
                                                $doc_info[0]
9126
                                            );
9127
9128
                                            $docSysPartPathNoCourseCode = str_replace(
9129
                                                $_course['directory'].'/',
9130
                                                '',
9131
                                                $docSysPartPath
9132
                                            );
9133
9134
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
9135
                                            if (file_exists($docSysPath)) {
9136
                                                $file_path = $docSysPartPathNoCourseCode;
9137
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
9138
                                                $link_updates[$my_file_path][] = [
9139
                                                    'orig' => $doc_info[0],
9140
                                                    'dest' => $file_path,
9141
                                                ];
9142
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9143
                                                $my_dep->setAttribute('xml:base', '');
9144
                                            }
9145
                                        }
9146
                                        break;
9147
                                    case 'rel':
9148
                                        // Path relative to the current document. Save xml:base as current document's
9149
                                        // directory and save file in zip as subdir.file_path
9150
                                        if ('..' === substr($doc_info[0], 0, 2)) {
9151
                                            // Relative path going up.
9152
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
9153
                                            $current_dir = str_replace('\\', '/', $current_dir);
9154
                                            $file_path = realpath($current_dir.$doc_info[0]);
9155
                                            $file_path = str_replace('\\', '/', $file_path);
9156
                                            if (false !== strstr($file_path, $main_path)) {
9157
                                                // The calculated real path is really inside Chamilo's root path.
9158
                                                // Reduce file path to what's under the DocumentRoot.
9159
9160
                                                $file_path = substr($file_path, strlen($root_path));
9161
                                                $file_path_dest = $file_path;
9162
9163
                                                // File path is courses/CHAMILO/document/....
9164
                                                $info_file_path = explode('/', $file_path);
9165
                                                if ('courses' == $info_file_path[0]) {
9166
                                                    // Add character "/" in file path.
9167
                                                    $file_path_dest = 'document/'.$file_path;
9168
                                                }
9169
                                                $zip_files_abs[] = $file_path;
9170
9171
                                                $link_updates[$my_file_path][] = [
9172
                                                    'orig' => $doc_info[0],
9173
                                                    'dest' => $file_path_dest,
9174
                                                ];
9175
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9176
                                                $my_dep->setAttribute('xml:base', '');
9177
                                            }
9178
                                        } else {
9179
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
9180
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
9181
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
9182
                                        }
9183
                                        break;
9184
                                    default:
9185
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
9186
                                        $my_dep->setAttribute('xml:base', '');
9187
                                        break;
9188
                                }
9189
                            }
9190
                            $my_dep->appendChild($my_dep_file);
9191
                            $resources->appendChild($my_dep);
9192
                            $dependency = $xmldoc->createElement('dependency');
9193
                            $dependency->setAttribute('identifierref', $res_id);
9194
                            $my_resource->appendChild($dependency);
9195
                            $i++;
9196
                        }
9197
                        $resources->appendChild($my_resource);
9198
                        $zip_files[] = $my_file_path;
9199
                        break;
9200
                    default:
9201
                        // Get the path of the file(s) from the course directory root
9202
                        $my_file_path = 'non_exportable.html';
9203
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
9204
                        $my_xml_file_path = $my_file_path;
9205
                        $my_sub_dir = dirname($my_file_path);
9206
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
9207
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
9208
                        $my_xml_sub_dir = $my_sub_dir;
9209
                        // Give a <resource> child to the <resources> element.
9210
                        $my_resource = $xmldoc->createElement('resource');
9211
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
9212
                        $my_resource->setAttribute('type', 'webcontent');
9213
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
9214
                        // adlcp:scormtype can be either 'sco' or 'asset'.
9215
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
9216
                        // xml:base is the base directory to find the files declared in this resource.
9217
                        $my_resource->setAttribute('xml:base', '');
9218
                        // Give a <file> child to the <resource> element.
9219
                        $my_file = $xmldoc->createElement('file');
9220
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
9221
                        $my_resource->appendChild($my_file);
9222
                        $resources->appendChild($my_resource);
9223
                        break;
9224
                }
9225
            }
9226
        }
9227
        $organizations->appendChild($organization);
9228
        $root->appendChild($organizations);
9229
        $root->appendChild($resources);
9230
        $xmldoc->appendChild($root);
9231
9232
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
9233
9234
        // then add the file to the zip, then destroy the file (this is done automatically).
9235
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
9236
        foreach ($zip_files as $file_path) {
9237
            if (empty($file_path)) {
9238
                continue;
9239
            }
9240
9241
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
9242
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
9243
9244
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
9245
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
9246
            }
9247
9248
            $this->create_path($dest_file);
9249
            @copy($filePath, $dest_file);
9250
9251
            // Check if the file needs a link update.
9252
            if (in_array($file_path, array_keys($link_updates))) {
9253
                $string = file_get_contents($dest_file);
9254
                unlink($dest_file);
9255
                foreach ($link_updates[$file_path] as $old_new) {
9256
                    // This is an ugly hack that allows .flv files to be found by the flv player that
9257
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
9258
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
9259
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
9260
                    if ('flv' === substr($old_new['dest'], -3) &&
9261
                        'main/' === substr($old_new['dest'], 0, 5)
9262
                    ) {
9263
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
9264
                    } elseif ('flv' === substr($old_new['dest'], -3) &&
9265
                        'video/' === substr($old_new['dest'], 0, 6)
9266
                    ) {
9267
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
9268
                    }
9269
9270
                    // Fix to avoid problems with default_course_document
9271
                    if (false === strpos('main/default_course_document', $old_new['dest'])) {
9272
                        $newDestination = $old_new['dest'];
9273
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
9274
                            $newDestination = $old_new['replace'];
9275
                        }
9276
                    } else {
9277
                        $newDestination = str_replace('document/', '', $old_new['dest']);
9278
                    }
9279
                    $string = str_replace($old_new['orig'], $newDestination, $string);
9280
9281
                    // Add files inside the HTMLs
9282
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
9283
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
9284
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
9285
                        copy(
9286
                            $sys_course_path.$new_path,
9287
                            $destinationFile
9288
                        );
9289
                    }
9290
                }
9291
                file_put_contents($dest_file, $string);
9292
            }
9293
9294
            if (file_exists($filePath) && $copyAll) {
9295
                $extension = $this->get_extension($filePath);
9296
                if (in_array($extension, ['html', 'html'])) {
9297
                    $containerOrigin = dirname($filePath);
9298
                    $containerDestination = dirname($dest_file);
9299
9300
                    $finder = new Finder();
9301
                    $finder->files()->in($containerOrigin)
9302
                        ->notName('*_DELETED_*')
9303
                        ->exclude('share_folder')
9304
                        ->exclude('chat_files')
9305
                        ->exclude('certificates')
9306
                    ;
9307
9308
                    if (is_dir($containerOrigin) &&
9309
                        is_dir($containerDestination)
9310
                    ) {
9311
                        $fs = new Filesystem();
9312
                        $fs->mirror(
9313
                            $containerOrigin,
9314
                            $containerDestination,
9315
                            $finder
9316
                        );
9317
                    }
9318
                }
9319
            }
9320
        }
9321
9322
        foreach ($zip_files_abs as $file_path) {
9323
            if (empty($file_path)) {
9324
                continue;
9325
            }
9326
9327
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
9328
                continue;
9329
            }
9330
9331
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
9332
            if (false !== strstr($file_path, 'upload/users')) {
9333
                $pos = strpos($file_path, 'my_files/');
9334
                if (false !== $pos) {
9335
                    $onlyDirectory = str_replace(
9336
                        'upload/users/',
9337
                        '',
9338
                        substr($file_path, $pos, strlen($file_path))
9339
                    );
9340
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
9341
                }
9342
            }
9343
9344
            if (false !== strstr($file_path, 'default_course_document/')) {
9345
                $replace = str_replace('/main', '', $file_path);
9346
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
9347
            }
9348
9349
            if (empty($dest_file)) {
9350
                continue;
9351
            }
9352
9353
            $this->create_path($dest_file);
9354
            copy($main_path.$file_path, $dest_file);
9355
            // Check if the file needs a link update.
9356
            if (in_array($file_path, array_keys($link_updates))) {
9357
                $string = file_get_contents($dest_file);
9358
                unlink($dest_file);
9359
                foreach ($link_updates[$file_path] as $old_new) {
9360
                    // This is an ugly hack that allows .flv files to be found by the flv player that
9361
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
9362
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
9363
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
9364
                    if ('flv' == substr($old_new['dest'], -3) &&
9365
                        'main/' == substr($old_new['dest'], 0, 5)
9366
                    ) {
9367
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
9368
                    }
9369
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
9370
                }
9371
                file_put_contents($dest_file, $string);
9372
            }
9373
        }
9374
9375
        if (is_array($links_to_create)) {
9376
            foreach ($links_to_create as $file => $link) {
9377
                $content = '<!DOCTYPE html><head>
9378
                            <meta charset="'.api_get_language_isocode().'" />
9379
                            <title>'.$link['title'].'</title>
9380
                            </head>
9381
                            <body dir="'.api_get_text_direction().'">
9382
                            <div style="text-align:center">
9383
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
9384
                            </body>
9385
                            </html>';
9386
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
9387
            }
9388
        }
9389
9390
        // Add non exportable message explanation.
9391
        $lang_not_exportable = get_lang('This learning object or activity is not SCORM compliant. That\'s why it is not exportable.');
9392
        $file_content = '<!DOCTYPE html><head>
9393
                        <meta charset="'.api_get_language_isocode().'" />
9394
                        <title>'.$lang_not_exportable.'</title>
9395
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
9396
                        </head>
9397
                        <body dir="'.api_get_text_direction().'">';
9398
        $file_content .=
9399
            <<<EOD
9400
                    <style>
9401
            .error-message {
9402
                font-family: arial, verdana, helvetica, sans-serif;
9403
                border-width: 1px;
9404
                border-style: solid;
9405
                left: 50%;
9406
                margin: 10px auto;
9407
                min-height: 30px;
9408
                padding: 5px;
9409
                right: 50%;
9410
                width: 500px;
9411
                background-color: #FFD1D1;
9412
                border-color: #FF0000;
9413
                color: #000;
9414
            }
9415
        </style>
9416
    <body>
9417
        <div class="error-message">
9418
            $lang_not_exportable
9419
        </div>
9420
    </body>
9421
</html>
9422
EOD;
9423
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
9424
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
9425
        }
9426
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
9427
9428
        // Add the extra files that go along with a SCORM package.
9429
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
9430
9431
        $fs = new Filesystem();
9432
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
9433
9434
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
9435
        $manifest = @$xmldoc->saveXML();
9436
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
9437
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
9438
        $zip_folder->add(
9439
            $archivePath.'/'.$temp_dir_short,
9440
            PCLZIP_OPT_REMOVE_PATH,
9441
            $archivePath.'/'.$temp_dir_short.'/'
9442
        );
9443
9444
        // Clean possible temporary files.
9445
        foreach ($files_cleanup as $file) {
9446
            $res = unlink($file);
9447
            if (false === $res) {
9448
                error_log(
9449
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
9450
                    0
9451
                );
9452
            }
9453
        }
9454
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
9455
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
9456
    }
9457
9458
    /**
9459
     * @param int $lp_id
9460
     *
9461
     * @return bool
9462
     */
9463
    public function scorm_export_to_pdf($lp_id)
9464
    {
9465
        $lp_id = (int) $lp_id;
9466
        $files_to_export = [];
9467
9468
        $sessionId = api_get_session_id();
9469
        $course_data = api_get_course_info($this->cc);
9470
9471
        $lp = Container::getLpRepository()->find($lp_id);
9472
        if (!empty($course_data)) {
9473
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
9474
            $list = self::get_flat_ordered_items_list($lp);
9475
            if (!empty($list)) {
9476
                foreach ($list as $item_id) {
9477
                    $item = $this->items[$item_id];
9478
                    switch ($item->type) {
9479
                        case 'document':
9480
                            // Getting documents from a LP with chamilo documents
9481
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
9482
                            // Try loading document from the base course.
9483
                            if (empty($file_data) && !empty($sessionId)) {
9484
                                $file_data = DocumentManager::get_document_data_by_id(
9485
                                    $item->path,
9486
                                    $this->cc,
9487
                                    false,
9488
                                    0
9489
                                );
9490
                            }
9491
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
9492
                            if (file_exists($file_path)) {
9493
                                $files_to_export[] = [
9494
                                    'title' => $item->get_title(),
9495
                                    'path' => $file_path,
9496
                                ];
9497
                            }
9498
                            break;
9499
                        case 'asset': //commes from a scorm package generated by chamilo
9500
                        case 'sco':
9501
                            $file_path = $scorm_path.'/'.$item->path;
9502
                            if (file_exists($file_path)) {
9503
                                $files_to_export[] = [
9504
                                    'title' => $item->get_title(),
9505
                                    'path' => $file_path,
9506
                                ];
9507
                            }
9508
                            break;
9509
                        case 'dir':
9510
                            $files_to_export[] = [
9511
                                'title' => $item->get_title(),
9512
                                'path' => null,
9513
                            ];
9514
                            break;
9515
                    }
9516
                }
9517
            }
9518
9519
            $pdf = new PDF();
9520
            $result = $pdf->html_to_pdf(
9521
                $files_to_export,
9522
                $this->name,
9523
                $this->cc,
9524
                true,
9525
                true,
9526
                true,
9527
                $this->get_name()
9528
            );
9529
9530
            return $result;
9531
        }
9532
9533
        return false;
9534
    }
9535
9536
    /**
9537
     * Temp function to be moved in main_api or the best place around for this.
9538
     * Creates a file path if it doesn't exist.
9539
     *
9540
     * @param string $path
9541
     */
9542
    public function create_path($path)
9543
    {
9544
        $path_bits = explode('/', dirname($path));
9545
9546
        // IS_WINDOWS_OS has been defined in main_api.lib.php
9547
        $path_built = IS_WINDOWS_OS ? '' : '/';
9548
        foreach ($path_bits as $bit) {
9549
            if (!empty($bit)) {
9550
                $new_path = $path_built.$bit;
9551
                if (is_dir($new_path)) {
9552
                    $path_built = $new_path.'/';
9553
                } else {
9554
                    mkdir($new_path, api_get_permissions_for_new_directories());
9555
                    $path_built = $new_path.'/';
9556
                }
9557
            }
9558
        }
9559
    }
9560
9561
    /**
9562
     * @param int    $lp_id
9563
     * @param string $status
9564
     */
9565
    public function set_autolaunch($lp_id, $status)
9566
    {
9567
        $course_id = api_get_course_int_id();
9568
        $lp_id = (int) $lp_id;
9569
        $status = (int) $status;
9570
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
9571
9572
        // Setting everything to autolaunch = 0
9573
        $attributes['autolaunch'] = 0;
9574
        $where = [
9575
            'session_id = ? AND c_id = ? ' => [
9576
                api_get_session_id(),
9577
                $course_id,
9578
            ],
9579
        ];
9580
        Database::update($lp_table, $attributes, $where);
9581
        if (1 == $status) {
9582
            //Setting my lp_id to autolaunch = 1
9583
            $attributes['autolaunch'] = 1;
9584
            $where = [
9585
                'iid = ? AND session_id = ? AND c_id = ?' => [
9586
                    $lp_id,
9587
                    api_get_session_id(),
9588
                    $course_id,
9589
                ],
9590
            ];
9591
            Database::update($lp_table, $attributes, $where);
9592
        }
9593
    }
9594
9595
    /**
9596
     * Gets previous_item_id for the next element of the lp_item table.
9597
     *
9598
     * @author Isaac flores paz
9599
     *
9600
     * @return int Previous item ID
9601
     */
9602
    public function select_previous_item_id()
9603
    {
9604
        $course_id = api_get_course_int_id();
9605
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9606
9607
        // Get the max order of the items
9608
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
9609
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9610
        $rs_max_order = Database::query($sql);
9611
        $row_max_order = Database::fetch_object($rs_max_order);
9612
        $max_order = $row_max_order->display_order;
9613
        // Get the previous item ID
9614
        $sql = "SELECT iid as previous FROM $table_lp_item
9615
                WHERE
9616
                    c_id = $course_id AND
9617
                    lp_id = ".$this->lp_id." AND
9618
                    display_order = '$max_order' ";
9619
        $rs_max = Database::query($sql);
9620
        $row_max = Database::fetch_object($rs_max);
9621
9622
        // Return the previous item ID
9623
        return $row_max->previous;
9624
    }
9625
9626
    /**
9627
     * Copies an LP.
9628
     */
9629
    public function copy()
9630
    {
9631
        // Course builder
9632
        $cb = new CourseBuilder();
9633
9634
        //Setting tools that will be copied
9635
        $cb->set_tools_to_build(['learnpaths']);
9636
9637
        //Setting elements that will be copied
9638
        $cb->set_tools_specific_id_list(
9639
            ['learnpaths' => [$this->lp_id]]
9640
        );
9641
9642
        $course = $cb->build();
9643
9644
        //Course restorer
9645
        $course_restorer = new CourseRestorer($course);
9646
        $course_restorer->set_add_text_in_items(true);
9647
        $course_restorer->set_tool_copy_settings(
9648
            ['learnpaths' => ['reset_dates' => true]]
9649
        );
9650
        $course_restorer->restore(
9651
            api_get_course_id(),
9652
            api_get_session_id(),
9653
            false,
9654
            false
9655
        );
9656
    }
9657
9658
    /**
9659
     * Verify document size.
9660
     *
9661
     * @param string $s
9662
     *
9663
     * @return bool
9664
     */
9665
    public static function verify_document_size($s)
9666
    {
9667
        $post_max = ini_get('post_max_size');
9668
        if ('M' == substr($post_max, -1, 1)) {
9669
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
9670
        } elseif ('G' == substr($post_max, -1, 1)) {
9671
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
9672
        }
9673
        $upl_max = ini_get('upload_max_filesize');
9674
        if ('M' == substr($upl_max, -1, 1)) {
9675
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
9676
        } elseif ('G' == substr($upl_max, -1, 1)) {
9677
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
9678
        }
9679
9680
        $repo = Container::getDocumentRepository();
9681
        $documents_total_space = $repo->getTotalSpace(api_get_course_int_id());
9682
9683
        $course_max_space = DocumentManager::get_course_quota();
9684
        $total_size = filesize($s) + $documents_total_space;
9685
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
9686
            return true;
9687
        }
9688
9689
        return false;
9690
    }
9691
9692
    /**
9693
     * Clear LP prerequisites.
9694
     */
9695
    public function clearPrerequisites()
9696
    {
9697
        $course_id = $this->get_course_int_id();
9698
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9699
        $lp_id = $this->get_id();
9700
        // Cleaning prerequisites
9701
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
9702
                WHERE lp_id = $lp_id";
9703
        Database::query($sql);
9704
9705
        // Cleaning mastery score for exercises
9706
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
9707
                WHERE lp_id = $lp_id AND item_type = 'quiz'";
9708
        Database::query($sql);
9709
    }
9710
9711
    public function set_previous_step_as_prerequisite_for_all_items()
9712
    {
9713
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9714
        $course_id = $this->get_course_int_id();
9715
        $lp_id = $this->get_id();
9716
9717
        if (!empty($this->items)) {
9718
            $previous_item_id = null;
9719
            $previous_item_max = 0;
9720
            $previous_item_type = null;
9721
            $last_item_not_dir = null;
9722
            $last_item_not_dir_type = null;
9723
            $last_item_not_dir_max = null;
9724
9725
            foreach ($this->ordered_items as $itemId) {
9726
                $item = $this->getItem($itemId);
9727
                // if there was a previous item... (otherwise jump to set it)
9728
                if (!empty($previous_item_id)) {
9729
                    $current_item_id = $item->get_id(); //save current id
9730
                    if ('dir' != $item->get_type()) {
9731
                        // Current item is not a folder, so it qualifies to get a prerequisites
9732
                        if ('quiz' == $last_item_not_dir_type) {
9733
                            // if previous is quiz, mark its max score as default score to be achieved
9734
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
9735
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
9736
                            Database::query($sql);
9737
                        }
9738
                        // now simply update the prerequisite to set it to the last non-chapter item
9739
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
9740
                                WHERE lp_id = $lp_id AND iid = $current_item_id";
9741
                        Database::query($sql);
9742
                        // record item as 'non-chapter' reference
9743
                        $last_item_not_dir = $item->get_id();
9744
                        $last_item_not_dir_type = $item->get_type();
9745
                        $last_item_not_dir_max = $item->get_max();
9746
                    }
9747
                } else {
9748
                    if ('dir' != $item->get_type()) {
9749
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
9750
                        $last_item_not_dir = $item->get_id();
9751
                        $last_item_not_dir_type = $item->get_type();
9752
                        $last_item_not_dir_max = $item->get_max();
9753
                    }
9754
                }
9755
                // Saving the item as "previous item" for the next loop
9756
                $previous_item_id = $item->get_id();
9757
                $previous_item_max = $item->get_max();
9758
                $previous_item_type = $item->get_type();
9759
            }
9760
        }
9761
    }
9762
9763
    /**
9764
     * @param array $params
9765
     *
9766
     * @return int
9767
     */
9768
    public static function createCategory($params)
9769
    {
9770
        $courseEntity = api_get_course_entity(api_get_course_int_id());
9771
9772
        $item = new CLpCategory();
9773
        $item
9774
            ->setName($params['name'])
9775
            ->setParent($courseEntity)
9776
            ->addCourseLink($courseEntity, api_get_session_entity())
9777
        ;
9778
9779
        $repo = Container::getLpCategoryRepository();
9780
        $repo->create($item);
9781
9782
        return $item->getIid();
9783
    }
9784
9785
    /**
9786
     * @param array $params
9787
     */
9788
    public static function updateCategory($params)
9789
    {
9790
        $em = Database::getManager();
9791
        /** @var CLpCategory $item */
9792
        $item = $em->find(CLpCategory::class, $params['id']);
9793
        if ($item) {
9794
            $item->setName($params['name']);
9795
            $em->persist($item);
9796
            $em->flush();
9797
        }
9798
    }
9799
9800
    /**
9801
     * @param int $id
9802
     */
9803
    public static function moveUpCategory($id)
9804
    {
9805
        $id = (int) $id;
9806
        $em = Database::getManager();
9807
        /** @var CLpCategory $item */
9808
        $item = $em->find(CLpCategory::class, $id);
9809
        if ($item) {
9810
            $position = $item->getPosition() - 1;
9811
            $item->setPosition($position);
9812
            $em->persist($item);
9813
            $em->flush();
9814
        }
9815
    }
9816
9817
    /**
9818
     * @param int $id
9819
     */
9820
    public static function moveDownCategory($id)
9821
    {
9822
        $id = (int) $id;
9823
        $em = Database::getManager();
9824
        /** @var CLpCategory $item */
9825
        $item = $em->find(CLpCategory::class, $id);
9826
        if ($item) {
9827
            $position = $item->getPosition() + 1;
9828
            $item->setPosition($position);
9829
            $em->persist($item);
9830
            $em->flush();
9831
        }
9832
    }
9833
9834
    /**
9835
     * @param int $courseId
9836
     *
9837
     * @return int|mixed
9838
     */
9839
    public static function getCountCategories($courseId)
9840
    {
9841
        if (empty($courseId)) {
9842
            return 0;
9843
        }
9844
        $repo = Container::getLpCategoryRepository();
9845
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId));
9846
        $qb->addSelect('count(resource)');
9847
9848
        return $qb->getSingleScalarResult();
9849
    }
9850
9851
    /**
9852
     * @param int $courseId
9853
     *
9854
     * @return CLpCategory[]
9855
     */
9856
    public static function getCategories($courseId)
9857
    {
9858
        $em = Database::getManager();
9859
9860
        // Using doctrine extensions
9861
        $repo = Container::getLpCategoryRepository();
9862
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId));
9863
9864
        return $qb->getQuery()->getResult();
9865
9866
        //return $repo->getBySortableGroupsQuery(['cId' => $courseId])->getResult();
9867
    }
9868
9869
    public static function getCategorySessionId($id)
9870
    {
9871
        if (false === api_get_configuration_value('allow_session_lp_category')) {
9872
            return 0;
9873
        }
9874
9875
        $table = Database::get_course_table(TABLE_LP_CATEGORY);
9876
        $id = (int) $id;
9877
9878
        $sql = "SELECT session_id FROM $table WHERE iid = $id";
9879
        $result = Database::query($sql);
9880
        $result = Database::fetch_array($result, 'ASSOC');
9881
9882
        if ($result) {
9883
            return (int) $result['session_id'];
9884
        }
9885
9886
        return 0;
9887
    }
9888
9889
    /**
9890
     * @param int $id
9891
     */
9892
    public static function deleteCategory($id): bool
9893
    {
9894
        $repo = Container::getLpCategoryRepository();
9895
        /** @var CLpCategory $category */
9896
        $category = $repo->find($id);
9897
        if ($category) {
9898
            $em = Database::getManager();
9899
            $lps = $category->getLps();
9900
9901
            foreach ($lps as $lp) {
9902
                $lp->setCategory(null);
9903
                $em->persist($lp);
9904
            }
9905
9906
            // Removing category.
9907
            $em->remove($category);
9908
            $em->flush();
9909
9910
            return true;
9911
        }
9912
9913
        return false;
9914
    }
9915
9916
    /**
9917
     * @param int  $courseId
9918
     * @param bool $addSelectOption
9919
     *
9920
     * @return array
9921
     */
9922
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
9923
    {
9924
        $repo = Container::getLpCategoryRepository();
9925
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId));
9926
        $items = $qb->getQuery()->getResult();
9927
9928
        $cats = [];
9929
        if ($addSelectOption) {
9930
            $cats = [get_lang('Select a category')];
9931
        }
9932
9933
        if (!empty($items)) {
9934
            foreach ($items as $cat) {
9935
                $cats[$cat->getIid()] = $cat->getName();
9936
            }
9937
        }
9938
9939
        return $cats;
9940
    }
9941
9942
    /**
9943
     * @param string $courseCode
9944
     * @param int    $lpId
9945
     * @param int    $user_id
9946
     *
9947
     * @return learnpath
9948
     */
9949
    public static function getLpFromSession($courseCode, $lpId, $user_id)
9950
    {
9951
        $debug = 0;
9952
        $learnPath = null;
9953
        $lpObject = Session::read('lpobject');
9954
9955
        $repo = Container::getLpRepository();
9956
        $lp = $repo->find($lpId);
9957
        if (null !== $lpObject) {
9958
            /** @var learnpath $learnPath */
9959
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
9960
            $learnPath->entity = $lp;
9961
            if ($debug) {
9962
                error_log('getLpFromSession: unserialize');
9963
                error_log('------getLpFromSession------');
9964
                error_log('------unserialize------');
9965
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
9966
                error_log("api_get_sessionid: ".api_get_session_id());
9967
            }
9968
        }
9969
9970
        if (!is_object($learnPath)) {
9971
            $learnPath = new learnpath($lp, api_get_course_info($courseCode), $user_id);
9972
            if ($debug) {
9973
                error_log('------getLpFromSession------');
9974
                error_log('getLpFromSession: create new learnpath');
9975
                error_log("create new LP with $courseCode - $lpId - $user_id");
9976
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
9977
                error_log("api_get_sessionid: ".api_get_session_id());
9978
            }
9979
        }
9980
9981
        return $learnPath;
9982
    }
9983
9984
    /**
9985
     * @param int $itemId
9986
     *
9987
     * @return learnpathItem|false
9988
     */
9989
    public function getItem($itemId)
9990
    {
9991
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
9992
            return $this->items[$itemId];
9993
        }
9994
9995
        return false;
9996
    }
9997
9998
    /**
9999
     * @return int
10000
     */
10001
    public function getCurrentAttempt()
10002
    {
10003
        $attempt = $this->getItem($this->get_current_item_id());
10004
        if ($attempt) {
10005
            return $attempt->get_attempt_id();
10006
        }
10007
10008
        return 0;
10009
    }
10010
10011
    /**
10012
     * @return int
10013
     */
10014
    public function getCategoryId()
10015
    {
10016
        return (int) $this->categoryId;
10017
    }
10018
10019
    /**
10020
     * Get whether this is a learning path with the possibility to subscribe
10021
     * users or not.
10022
     *
10023
     * @return int
10024
     */
10025
    public function getSubscribeUsers()
10026
    {
10027
        return $this->subscribeUsers;
10028
    }
10029
10030
    /**
10031
     * Calculate the count of stars for a user in this LP
10032
     * This calculation is based on the following rules:
10033
     * - the student gets one star when he gets to 50% of the learning path
10034
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
10035
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
10036
     * - the student gets the final star when the score for the *last* test is >= 80%.
10037
     *
10038
     * @param int $sessionId Optional. The session ID
10039
     *
10040
     * @return int The count of stars
10041
     */
10042
    public function getCalculateStars($sessionId = 0)
10043
    {
10044
        $stars = 0;
10045
        $progress = self::getProgress(
10046
            $this->lp_id,
10047
            $this->user_id,
10048
            $this->course_int_id,
10049
            $sessionId
10050
        );
10051
10052
        if ($progress >= 50) {
10053
            $stars++;
10054
        }
10055
10056
        // Calculate stars chapters evaluation
10057
        $exercisesItems = $this->getExercisesItems();
10058
10059
        if (!empty($exercisesItems)) {
10060
            $totalResult = 0;
10061
10062
            foreach ($exercisesItems as $exerciseItem) {
10063
                $exerciseResultInfo = Event::getExerciseResultsByUser(
10064
                    $this->user_id,
10065
                    $exerciseItem->path,
10066
                    $this->course_int_id,
10067
                    $sessionId,
10068
                    $this->lp_id,
10069
                    $exerciseItem->db_id
10070
                );
10071
10072
                $exerciseResultInfo = end($exerciseResultInfo);
10073
10074
                if (!$exerciseResultInfo) {
10075
                    continue;
10076
                }
10077
10078
                if (!empty($exerciseResultInfo['max_score'])) {
10079
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
10080
                } else {
10081
                    $exerciseResult = 0;
10082
                }
10083
                $totalResult += $exerciseResult;
10084
            }
10085
10086
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
10087
10088
            if ($totalExerciseAverage >= 50) {
10089
                $stars++;
10090
            }
10091
10092
            if ($totalExerciseAverage >= 80) {
10093
                $stars++;
10094
            }
10095
        }
10096
10097
        // Calculate star for final evaluation
10098
        $finalEvaluationItem = $this->getFinalEvaluationItem();
10099
10100
        if (!empty($finalEvaluationItem)) {
10101
            $evaluationResultInfo = Event::getExerciseResultsByUser(
10102
                $this->user_id,
10103
                $finalEvaluationItem->path,
10104
                $this->course_int_id,
10105
                $sessionId,
10106
                $this->lp_id,
10107
                $finalEvaluationItem->db_id
10108
            );
10109
10110
            $evaluationResultInfo = end($evaluationResultInfo);
10111
10112
            if ($evaluationResultInfo) {
10113
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
10114
                if ($evaluationResult >= 80) {
10115
                    $stars++;
10116
                }
10117
            }
10118
        }
10119
10120
        return $stars;
10121
    }
10122
10123
    /**
10124
     * Get the items of exercise type.
10125
     *
10126
     * @return array The items. Otherwise return false
10127
     */
10128
    public function getExercisesItems()
10129
    {
10130
        $exercises = [];
10131
        foreach ($this->items as $item) {
10132
            if ('quiz' != $item->type) {
10133
                continue;
10134
            }
10135
            $exercises[] = $item;
10136
        }
10137
10138
        array_pop($exercises);
10139
10140
        return $exercises;
10141
    }
10142
10143
    /**
10144
     * Get the item of exercise type (evaluation type).
10145
     *
10146
     * @return array The final evaluation. Otherwise return false
10147
     */
10148
    public function getFinalEvaluationItem()
10149
    {
10150
        $exercises = [];
10151
        foreach ($this->items as $item) {
10152
            if (TOOL_QUIZ !== $item->type) {
10153
                continue;
10154
            }
10155
10156
            $exercises[] = $item;
10157
        }
10158
10159
        return array_pop($exercises);
10160
    }
10161
10162
    /**
10163
     * Calculate the total points achieved for the current user in this learning path.
10164
     *
10165
     * @param int $sessionId Optional. The session Id
10166
     *
10167
     * @return int
10168
     */
10169
    public function getCalculateScore($sessionId = 0)
10170
    {
10171
        // Calculate stars chapters evaluation
10172
        $exercisesItems = $this->getExercisesItems();
10173
        $finalEvaluationItem = $this->getFinalEvaluationItem();
10174
        $totalExercisesResult = 0;
10175
        $totalEvaluationResult = 0;
10176
10177
        if (false !== $exercisesItems) {
10178
            foreach ($exercisesItems as $exerciseItem) {
10179
                $exerciseResultInfo = Event::getExerciseResultsByUser(
10180
                    $this->user_id,
10181
                    $exerciseItem->path,
10182
                    $this->course_int_id,
10183
                    $sessionId,
10184
                    $this->lp_id,
10185
                    $exerciseItem->db_id
10186
                );
10187
10188
                $exerciseResultInfo = end($exerciseResultInfo);
10189
10190
                if (!$exerciseResultInfo) {
10191
                    continue;
10192
                }
10193
10194
                $totalExercisesResult += $exerciseResultInfo['score'];
10195
            }
10196
        }
10197
10198
        if (!empty($finalEvaluationItem)) {
10199
            $evaluationResultInfo = Event::getExerciseResultsByUser(
10200
                $this->user_id,
10201
                $finalEvaluationItem->path,
10202
                $this->course_int_id,
10203
                $sessionId,
10204
                $this->lp_id,
10205
                $finalEvaluationItem->db_id
10206
            );
10207
10208
            $evaluationResultInfo = end($evaluationResultInfo);
10209
10210
            if ($evaluationResultInfo) {
10211
                $totalEvaluationResult += $evaluationResultInfo['score'];
10212
            }
10213
        }
10214
10215
        return $totalExercisesResult + $totalEvaluationResult;
10216
    }
10217
10218
    /**
10219
     * Check if URL is not allowed to be show in a iframe.
10220
     *
10221
     * @param string $src
10222
     *
10223
     * @return string
10224
     */
10225
    public function fixBlockedLinks($src)
10226
    {
10227
        $urlInfo = parse_url($src);
10228
10229
        $platformProtocol = 'https';
10230
        if (false === strpos(api_get_path(WEB_CODE_PATH), 'https')) {
10231
            $platformProtocol = 'http';
10232
        }
10233
10234
        $protocolFixApplied = false;
10235
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
10236
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
10237
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
10238
10239
        if ($platformProtocol != $scheme) {
10240
            Session::write('x_frame_source', $src);
10241
            $src = 'blank.php?error=x_frames_options';
10242
            $protocolFixApplied = true;
10243
        }
10244
10245
        if (false == $protocolFixApplied) {
10246
            if (false === strpos(api_get_path(WEB_PATH), $host)) {
10247
                // Check X-Frame-Options
10248
                $ch = curl_init();
10249
                $options = [
10250
                    CURLOPT_URL => $src,
10251
                    CURLOPT_RETURNTRANSFER => true,
10252
                    CURLOPT_HEADER => true,
10253
                    CURLOPT_FOLLOWLOCATION => true,
10254
                    CURLOPT_ENCODING => "",
10255
                    CURLOPT_AUTOREFERER => true,
10256
                    CURLOPT_CONNECTTIMEOUT => 120,
10257
                    CURLOPT_TIMEOUT => 120,
10258
                    CURLOPT_MAXREDIRS => 10,
10259
                ];
10260
10261
                $proxySettings = api_get_configuration_value('proxy_settings');
10262
                if (!empty($proxySettings) &&
10263
                    isset($proxySettings['curl_setopt_array'])
10264
                ) {
10265
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
10266
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
10267
                }
10268
10269
                curl_setopt_array($ch, $options);
10270
                $response = curl_exec($ch);
10271
                $httpCode = curl_getinfo($ch);
10272
                $headers = substr($response, 0, $httpCode['header_size']);
10273
10274
                $error = false;
10275
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
10276
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
10277
                ) {
10278
                    $error = true;
10279
                }
10280
10281
                if ($error) {
10282
                    Session::write('x_frame_source', $src);
10283
                    $src = 'blank.php?error=x_frames_options';
10284
                }
10285
            }
10286
        }
10287
10288
        return $src;
10289
    }
10290
10291
    /**
10292
     * Check if this LP has a created forum in the basis course.
10293
     *
10294
     * @deprecated
10295
     *
10296
     * @return bool
10297
     */
10298
    public function lpHasForum()
10299
    {
10300
        $forumTable = Database::get_course_table(TABLE_FORUM);
10301
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
10302
10303
        $fakeFrom = "
10304
            $forumTable f
10305
            INNER JOIN $itemProperty ip
10306
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
10307
        ";
10308
10309
        $resultData = Database::select(
10310
            'COUNT(f.iid) AS qty',
10311
            $fakeFrom,
10312
            [
10313
                'where' => [
10314
                    'ip.visibility != ? AND ' => 2,
10315
                    'ip.tool = ? AND ' => TOOL_FORUM,
10316
                    'f.c_id = ? AND ' => intval($this->course_int_id),
10317
                    'f.lp_id = ?' => intval($this->lp_id),
10318
                ],
10319
            ],
10320
            'first'
10321
        );
10322
10323
        return $resultData['qty'] > 0;
10324
    }
10325
10326
    /**
10327
     * Get the forum for this learning path.
10328
     *
10329
     * @param int $sessionId
10330
     *
10331
     * @return array
10332
     */
10333
    public function getForum($sessionId = 0)
10334
    {
10335
        $repo = Container::getForumRepository();
10336
10337
        $course = api_get_course_entity();
10338
        $session = api_get_session_entity($sessionId);
10339
        $qb = $repo->getResourcesByCourse($course, $session);
10340
10341
        return $qb->getQuery()->getResult();
10342
    }
10343
10344
    /**
10345
     * Create a forum for this learning path.
10346
     *
10347
     * @return int The forum ID if was created. Otherwise return false
10348
     */
10349
    public function createForum(CForumCategory $forumCategory)
10350
    {
10351
        return store_forum(
10352
            [
10353
                'lp_id' => $this->lp_id,
10354
                'forum_title' => $this->name,
10355
                'forum_comment' => null,
10356
                'forum_category' => $forumCategory->getIid(),
10357
                'students_can_edit_group' => ['students_can_edit' => 0],
10358
                'allow_new_threads_group' => ['allow_new_threads' => 0],
10359
                'default_view_type_group' => ['default_view_type' => 'flat'],
10360
                'group_forum' => 0,
10361
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
10362
            ],
10363
            [],
10364
            true
10365
        );
10366
    }
10367
10368
    /**
10369
     * Get the LP Final Item form.
10370
     *
10371
     * @throws Exception
10372
     *
10373
     *
10374
     * @return string
10375
     */
10376
    public function getFinalItemForm()
10377
    {
10378
        $finalItem = $this->getFinalItem();
10379
        $title = '';
10380
10381
        if ($finalItem) {
10382
            $title = $finalItem->get_title();
10383
            $buttonText = get_lang('Save');
10384
            $content = $this->getSavedFinalItem();
10385
        } else {
10386
            $buttonText = get_lang('Add this document to the course');
10387
            $content = $this->getFinalItemTemplate();
10388
        }
10389
10390
        $editorConfig = [
10391
            'ToolbarSet' => 'LearningPathDocuments',
10392
            'Width' => '100%',
10393
            'Height' => '500',
10394
            'FullPage' => true,
10395
//            'CreateDocumentDir' => $relative_prefix,
10396
    //            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
10397
  //          'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
10398
        ];
10399
10400
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
10401
            'type' => 'document',
10402
            'lp_id' => $this->lp_id,
10403
        ]);
10404
10405
        $form = new FormValidator('final_item', 'POST', $url);
10406
        $form->addText('title', get_lang('Title'));
10407
        $form->addButtonSave($buttonText);
10408
        $form->addHtml(
10409
            Display::return_message(
10410
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
10411
                'normal',
10412
                false
10413
            )
10414
        );
10415
10416
        $renderer = $form->defaultRenderer();
10417
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
10418
10419
        $form->addHtmlEditor(
10420
            'content_lp_certificate',
10421
            null,
10422
            true,
10423
            false,
10424
            $editorConfig,
10425
            true
10426
        );
10427
        $form->addHidden('action', 'add_final_item');
10428
        $form->addHidden('path', Session::read('pathItem'));
10429
        $form->addHidden('previous', $this->get_last());
10430
        $form->setDefaults(
10431
            ['title' => $title, 'content_lp_certificate' => $content]
10432
        );
10433
10434
        if ($form->validate()) {
10435
            $values = $form->exportValues();
10436
            $lastItemId = $this->getLastInFirstLevel();
10437
10438
            if (!$finalItem) {
10439
                $documentId = $this->create_document(
10440
                    $this->course_info,
10441
                    $values['content_lp_certificate'],
10442
                    $values['title']
10443
                );
10444
                $this->add_item(
10445
                    0,
10446
                    $lastItemId,
10447
                    'final_item',
10448
                    $documentId,
10449
                    $values['title'],
10450
                    ''
10451
                );
10452
10453
                Display::addFlash(
10454
                    Display::return_message(get_lang('Added'))
10455
                );
10456
            } else {
10457
                $this->edit_document($this->course_info);
10458
            }
10459
        }
10460
10461
        return $form->returnForm();
10462
    }
10463
10464
    /**
10465
     * Check if the current lp item is first, both, last or none from lp list.
10466
     *
10467
     * @param int $currentItemId
10468
     *
10469
     * @return string
10470
     */
10471
    public function isFirstOrLastItem($currentItemId)
10472
    {
10473
        $lpItemId = [];
10474
        $typeListNotToVerify = self::getChapterTypes();
10475
10476
        // Using get_toc() function instead $this->items because returns the correct order of the items
10477
        foreach ($this->get_toc() as $item) {
10478
            if (!in_array($item['type'], $typeListNotToVerify)) {
10479
                $lpItemId[] = $item['id'];
10480
            }
10481
        }
10482
10483
        $lastLpItemIndex = count($lpItemId) - 1;
10484
        $position = array_search($currentItemId, $lpItemId);
10485
10486
        switch ($position) {
10487
            case 0:
10488
                if (!$lastLpItemIndex) {
10489
                    $answer = 'both';
10490
                    break;
10491
                }
10492
10493
                $answer = 'first';
10494
                break;
10495
            case $lastLpItemIndex:
10496
                $answer = 'last';
10497
                break;
10498
            default:
10499
                $answer = 'none';
10500
        }
10501
10502
        return $answer;
10503
    }
10504
10505
    /**
10506
     * Get whether this is a learning path with the accumulated SCORM time or not.
10507
     *
10508
     * @return int
10509
     */
10510
    public function getAccumulateScormTime()
10511
    {
10512
        return $this->accumulateScormTime;
10513
    }
10514
10515
    /**
10516
     * Returns an HTML-formatted link to a resource, to incorporate directly into
10517
     * the new learning path tool.
10518
     *
10519
     * The function is a big switch on tool type.
10520
     * In each case, we query the corresponding table for information and build the link
10521
     * with that information.
10522
     *
10523
     * @author Yannick Warnier <[email protected]> - rebranding based on
10524
     * previous work (display_addedresource_link_in_learnpath())
10525
     *
10526
     * @param int $course_id      Course code
10527
     * @param int $learningPathId The learning path ID (in lp table)
10528
     * @param int $id_in_path     the unique index in the items table
10529
     * @param int $lpViewId
10530
     *
10531
     * @return string
10532
     */
10533
    public static function rl_get_resource_link_for_learnpath(
10534
        $course_id,
10535
        $learningPathId,
10536
        $id_in_path,
10537
        $lpViewId
10538
    ) {
10539
        $session_id = api_get_session_id();
10540
10541
        $learningPathId = (int) $learningPathId;
10542
        $id_in_path = (int) $id_in_path;
10543
        $lpViewId = (int) $lpViewId;
10544
10545
        $em = Database::getManager();
10546
        $lpItemRepo = $em->getRepository(CLpItem::class);
10547
10548
        /** @var CLpItem $rowItem */
10549
        $rowItem = $lpItemRepo->findOneBy([
10550
            'lp' => $learningPathId,
10551
            'iid' => $id_in_path,
10552
        ]);
10553
        $type = $rowItem->getItemType();
10554
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
10555
        $main_dir_path = api_get_path(WEB_CODE_PATH);
10556
        $link = '';
10557
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&sid='.$session_id;
10558
10559
        switch ($type) {
10560
            case 'dir':
10561
                return $main_dir_path.'lp/blank.php';
10562
            case TOOL_CALENDAR_EVENT:
10563
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
10564
            case TOOL_ANNOUNCEMENT:
10565
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
10566
            case TOOL_LINK:
10567
                $linkInfo = Link::getLinkInfo($id);
10568
                if (isset($linkInfo['url'])) {
10569
                    return $linkInfo['url'];
10570
                }
10571
10572
                return '';
10573
            case TOOL_QUIZ:
10574
                if (empty($id)) {
10575
                    return '';
10576
                }
10577
10578
                // Get the lp_item_view with the highest view_count.
10579
                $learnpathItemViewResult = $em
10580
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
10581
                    ->findBy(
10582
                        ['item' => $rowItem->getIid(), 'view' => $lpViewId],
10583
                        ['viewCount' => 'DESC'],
10584
                        1
10585
                    );
10586
                /** @var CLpItemView $learnpathItemViewData */
10587
                $learnpathItemViewData = current($learnpathItemViewResult);
10588
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getIid() : 0;
10589
10590
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
10591
                    .http_build_query([
10592
                        'lp_init' => 1,
10593
                        'learnpath_item_view_id' => $learnpathItemViewId,
10594
                        'learnpath_id' => $learningPathId,
10595
                        'learnpath_item_id' => $id_in_path,
10596
                        'exerciseId' => $id,
10597
                    ]);
10598
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
10599
                /*$TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
10600
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
10601
                $myrow = Database::fetch_array($result);
10602
                $path = $myrow['path'];
10603
10604
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
10605
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
10606
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;*/
10607
            case TOOL_FORUM:
10608
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
10609
            case TOOL_THREAD:
10610
                // forum post
10611
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
10612
                if (empty($id)) {
10613
                    return '';
10614
                }
10615
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND iid=$id";
10616
                $result = Database::query($sql);
10617
                $myrow = Database::fetch_array($result);
10618
10619
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
10620
                    .$extraParams;
10621
            case TOOL_POST:
10622
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
10623
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
10624
                $myrow = Database::fetch_array($result);
10625
10626
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
10627
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
10628
            case TOOL_READOUT_TEXT:
10629
                return api_get_path(WEB_CODE_PATH).
10630
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
10631
            case TOOL_DOCUMENT:
10632
                $repo = Container::getDocumentRepository();
10633
                $document = $repo->find($rowItem->getPath());
10634
                $params = [
10635
                    'cid' => $course_id,
10636
                    'sid' => $session_id,
10637
                ];
10638
                $file = $repo->getResourceFileUrl($document, $params, UrlGeneratorInterface::ABSOLUTE_URL);
10639
10640
                return $file;
10641
10642
                $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...
10643
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v'];
10644
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
10645
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
10646
10647
                $openmethod = 2;
10648
                $officedoc = false;
10649
                Session::write('openmethod', $openmethod);
10650
                Session::write('officedoc', $officedoc);
10651
10652
                if ($showDirectUrl) {
10653
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
10654
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
10655
                        if (Link::isPdfLink($file)) {
10656
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
10657
10658
                            return $pdfUrl;
10659
                        }
10660
                    }
10661
10662
                    return $file;
10663
                }
10664
10665
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
10666
            case TOOL_LP_FINAL_ITEM:
10667
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
10668
                    .$extraParams;
10669
            case 'assignments':
10670
                return $main_dir_path.'work/work.php?'.$extraParams;
10671
            case TOOL_DROPBOX:
10672
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
10673
            case 'introduction_text': //DEPRECATED
10674
                return '';
10675
            case TOOL_COURSE_DESCRIPTION:
10676
                return $main_dir_path.'course_description?'.$extraParams;
10677
            case TOOL_GROUP:
10678
                return $main_dir_path.'group/group.php?'.$extraParams;
10679
            case TOOL_USER:
10680
                return $main_dir_path.'user/user.php?'.$extraParams;
10681
            case TOOL_STUDENTPUBLICATION:
10682
                if (!empty($rowItem->getPath())) {
10683
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
10684
                }
10685
10686
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
10687
        }
10688
10689
        return $link;
10690
    }
10691
10692
    /**
10693
     * Gets the name of a resource (generally used in learnpath when no name is provided).
10694
     *
10695
     * @author Yannick Warnier <[email protected]>
10696
     *
10697
     * @param string $course_code    Course code
10698
     * @param int    $learningPathId
10699
     * @param int    $id_in_path     The resource ID
10700
     *
10701
     * @return string
10702
     */
10703
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
10704
    {
10705
        $_course = api_get_course_info($course_code);
10706
        if (empty($_course)) {
10707
            return '';
10708
        }
10709
        $course_id = $_course['real_id'];
10710
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10711
        $learningPathId = (int) $learningPathId;
10712
        $id_in_path = (int) $id_in_path;
10713
10714
        $sql = "SELECT item_type, title, ref
10715
                FROM $tbl_lp_item
10716
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
10717
        $res_item = Database::query($sql);
10718
10719
        if (Database::num_rows($res_item) < 1) {
10720
            return '';
10721
        }
10722
        $row_item = Database::fetch_array($res_item);
10723
        $type = strtolower($row_item['item_type']);
10724
        $id = $row_item['ref'];
10725
        $output = '';
10726
10727
        switch ($type) {
10728
            case TOOL_CALENDAR_EVENT:
10729
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
10730
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
10731
                $myrow = Database::fetch_array($result);
10732
                $output = $myrow['title'];
10733
                break;
10734
            case TOOL_ANNOUNCEMENT:
10735
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
10736
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
10737
                $myrow = Database::fetch_array($result);
10738
                $output = $myrow['title'];
10739
                break;
10740
            case TOOL_LINK:
10741
                // Doesn't take $target into account.
10742
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
10743
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
10744
                $myrow = Database::fetch_array($result);
10745
                $output = $myrow['title'];
10746
                break;
10747
            case TOOL_QUIZ:
10748
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
10749
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
10750
                $myrow = Database::fetch_array($result);
10751
                $output = $myrow['title'];
10752
                break;
10753
            case TOOL_FORUM:
10754
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
10755
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
10756
                $myrow = Database::fetch_array($result);
10757
                $output = $myrow['forum_name'];
10758
                break;
10759
            case TOOL_THREAD:
10760
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
10761
                // Grabbing the title of the post.
10762
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
10763
                $result_title = Database::query($sql_title);
10764
                $myrow_title = Database::fetch_array($result_title);
10765
                $output = $myrow_title['post_title'];
10766
                break;
10767
            case TOOL_POST:
10768
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
10769
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
10770
                $result = Database::query($sql);
10771
                $post = Database::fetch_array($result);
10772
                $output = $post['post_title'];
10773
                break;
10774
            case 'dir':
10775
            case TOOL_DOCUMENT:
10776
                $title = $row_item['title'];
10777
                $output = '-';
10778
                if (!empty($title)) {
10779
                    $output = $title;
10780
                }
10781
                break;
10782
            case 'hotpotatoes':
10783
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
10784
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
10785
                $myrow = Database::fetch_array($result);
10786
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
10787
                $last = count($pathname) - 1; // Making a correct name for the link.
10788
                $filename = $pathname[$last]; // Making a correct name for the link.
10789
                $myrow['path'] = rawurlencode($myrow['path']);
10790
                $output = $filename;
10791
                break;
10792
        }
10793
10794
        return stripslashes($output);
10795
    }
10796
10797
    /**
10798
     * Get the parent names for the current item.
10799
     *
10800
     * @param int $newItemId Optional. The item ID
10801
     *
10802
     * @return array
10803
     */
10804
    public function getCurrentItemParentNames($newItemId = 0)
10805
    {
10806
        $newItemId = $newItemId ?: $this->get_current_item_id();
10807
        $return = [];
10808
        $item = $this->getItem($newItemId);
10809
        $parent = $this->getItem($item->get_parent());
10810
10811
        while ($parent) {
10812
            $return[] = $parent->get_title();
10813
            $parent = $this->getItem($parent->get_parent());
10814
        }
10815
10816
        return array_reverse($return);
10817
    }
10818
10819
    /**
10820
     * Reads and process "lp_subscription_settings" setting.
10821
     *
10822
     * @return array
10823
     */
10824
    public static function getSubscriptionSettings()
10825
    {
10826
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
10827
        if (empty($subscriptionSettings)) {
10828
            // By default allow both settings
10829
            $subscriptionSettings = [
10830
                'allow_add_users_to_lp' => true,
10831
                'allow_add_users_to_lp_category' => true,
10832
            ];
10833
        } else {
10834
            $subscriptionSettings = $subscriptionSettings['options'];
10835
        }
10836
10837
        return $subscriptionSettings;
10838
    }
10839
10840
    /**
10841
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
10842
     */
10843
    public function exportToCourseBuildFormat()
10844
    {
10845
        if (!api_is_allowed_to_edit()) {
10846
            return false;
10847
        }
10848
10849
        $courseBuilder = new CourseBuilder();
10850
        $itemList = [];
10851
        /** @var learnpathItem $item */
10852
        foreach ($this->items as $item) {
10853
            $itemList[$item->get_type()][] = $item->get_path();
10854
        }
10855
10856
        if (empty($itemList)) {
10857
            return false;
10858
        }
10859
10860
        if (isset($itemList['document'])) {
10861
            // Get parents
10862
            foreach ($itemList['document'] as $documentId) {
10863
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
10864
                if (!empty($documentInfo['parents'])) {
10865
                    foreach ($documentInfo['parents'] as $parentInfo) {
10866
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
10867
                            continue;
10868
                        }
10869
                        $itemList['document'][] = $parentInfo['iid'];
10870
                    }
10871
                }
10872
            }
10873
10874
            $courseInfo = api_get_course_info();
10875
            foreach ($itemList['document'] as $documentId) {
10876
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
10877
                $items = DocumentManager::get_resources_from_source_html(
10878
                    $documentInfo['absolute_path'],
10879
                    true,
10880
                    TOOL_DOCUMENT
10881
                );
10882
10883
                if (!empty($items)) {
10884
                    foreach ($items as $item) {
10885
                        // Get information about source url
10886
                        $url = $item[0]; // url
10887
                        $scope = $item[1]; // scope (local, remote)
10888
                        $type = $item[2]; // type (rel, abs, url)
10889
10890
                        $origParseUrl = parse_url($url);
10891
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
10892
10893
                        if ('local' === $scope) {
10894
                            if ('abs' === $type || 'rel' === $type) {
10895
                                $documentFile = strstr($realOrigPath, 'document');
10896
                                if (false !== strpos($realOrigPath, $documentFile)) {
10897
                                    $documentFile = str_replace('document', '', $documentFile);
10898
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
10899
                                    // Document found! Add it to the list
10900
                                    if ($itemDocumentId) {
10901
                                        $itemList['document'][] = $itemDocumentId;
10902
                                    }
10903
                                }
10904
                            }
10905
                        }
10906
                    }
10907
                }
10908
            }
10909
10910
            $courseBuilder->build_documents(
10911
                api_get_session_id(),
10912
                $this->get_course_int_id(),
10913
                true,
10914
                $itemList['document']
10915
            );
10916
        }
10917
10918
        if (isset($itemList['quiz'])) {
10919
            $courseBuilder->build_quizzes(
10920
                api_get_session_id(),
10921
                $this->get_course_int_id(),
10922
                true,
10923
                $itemList['quiz']
10924
            );
10925
        }
10926
10927
        /*if (!empty($itemList['thread'])) {
10928
            $postList = [];
10929
            foreach ($itemList['thread'] as $postId) {
10930
                $post = get_post_information($postId);
10931
                if ($post) {
10932
                    if (!isset($itemList['forum'])) {
10933
                        $itemList['forum'] = [];
10934
                    }
10935
                    $itemList['forum'][] = $post['forum_id'];
10936
                    $postList[] = $postId;
10937
                }
10938
            }
10939
10940
            if (!empty($postList)) {
10941
                $courseBuilder->build_forum_posts(
10942
                    $this->get_course_int_id(),
10943
                    null,
10944
                    null,
10945
                    $postList
10946
                );
10947
            }
10948
        }*/
10949
10950
        if (!empty($itemList['thread'])) {
10951
            $threadList = [];
10952
            $em = Database::getManager();
10953
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
10954
            foreach ($itemList['thread'] as $threadId) {
10955
                /** @var CForumThread $thread */
10956
                $thread = $repo->find($threadId);
10957
                if ($thread) {
10958
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
10959
                    $threadList[] = $thread->getIid();
10960
                }
10961
            }
10962
10963
            if (!empty($threadList)) {
10964
                $courseBuilder->build_forum_topics(
10965
                    api_get_session_id(),
10966
                    $this->get_course_int_id(),
10967
                    null,
10968
                    $threadList
10969
                );
10970
            }
10971
        }
10972
10973
        $forumCategoryList = [];
10974
        if (isset($itemList['forum'])) {
10975
            foreach ($itemList['forum'] as $forumId) {
10976
                $forumInfo = get_forums($forumId);
10977
                $forumCategoryList[] = $forumInfo['forum_category'];
10978
            }
10979
        }
10980
10981
        if (!empty($forumCategoryList)) {
10982
            $courseBuilder->build_forum_category(
10983
                api_get_session_id(),
10984
                $this->get_course_int_id(),
10985
                true,
10986
                $forumCategoryList
10987
            );
10988
        }
10989
10990
        if (!empty($itemList['forum'])) {
10991
            $courseBuilder->build_forums(
10992
                api_get_session_id(),
10993
                $this->get_course_int_id(),
10994
                true,
10995
                $itemList['forum']
10996
            );
10997
        }
10998
10999
        if (isset($itemList['link'])) {
11000
            $courseBuilder->build_links(
11001
                api_get_session_id(),
11002
                $this->get_course_int_id(),
11003
                true,
11004
                $itemList['link']
11005
            );
11006
        }
11007
11008
        if (!empty($itemList['student_publication'])) {
11009
            $courseBuilder->build_works(
11010
                api_get_session_id(),
11011
                $this->get_course_int_id(),
11012
                true,
11013
                $itemList['student_publication']
11014
            );
11015
        }
11016
11017
        $courseBuilder->build_learnpaths(
11018
            api_get_session_id(),
11019
            $this->get_course_int_id(),
11020
            true,
11021
            [$this->get_id()],
11022
            false
11023
        );
11024
11025
        $courseBuilder->restoreDocumentsFromList();
11026
11027
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
11028
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
11029
        $result = DocumentManager::file_send_for_download(
11030
            $zipPath,
11031
            true,
11032
            $this->get_name().'.zip'
11033
        );
11034
11035
        if ($result) {
11036
            api_not_allowed();
11037
        }
11038
11039
        return true;
11040
    }
11041
11042
    /**
11043
     * Get whether this is a learning path with the accumulated work time or not.
11044
     *
11045
     * @return int
11046
     */
11047
    public function getAccumulateWorkTime()
11048
    {
11049
        return (int) $this->accumulateWorkTime;
11050
    }
11051
11052
    /**
11053
     * Get whether this is a learning path with the accumulated work time or not.
11054
     *
11055
     * @return int
11056
     */
11057
    public function getAccumulateWorkTimeTotalCourse()
11058
    {
11059
        $table = Database::get_course_table(TABLE_LP_MAIN);
11060
        $sql = "SELECT SUM(accumulate_work_time) AS total
11061
                FROM $table
11062
                WHERE c_id = ".$this->course_int_id;
11063
        $result = Database::query($sql);
11064
        $row = Database::fetch_array($result);
11065
11066
        return (int) $row['total'];
11067
    }
11068
11069
    /**
11070
     * @param int $lpId
11071
     * @param int $courseId
11072
     *
11073
     * @return mixed
11074
     */
11075
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
11076
    {
11077
        $lpId = (int) $lpId;
11078
        $table = Database::get_course_table(TABLE_LP_MAIN);
11079
        $sql = "SELECT accumulate_work_time
11080
                FROM $table
11081
                WHERE iid = $lpId";
11082
        $result = Database::query($sql);
11083
        $row = Database::fetch_array($result);
11084
11085
        return $row['accumulate_work_time'];
11086
    }
11087
11088
    /**
11089
     * @param int $courseId
11090
     *
11091
     * @return int
11092
     */
11093
    public static function getAccumulateWorkTimeTotal($courseId)
11094
    {
11095
        $table = Database::get_course_table(TABLE_LP_MAIN);
11096
        $courseId = (int) $courseId;
11097
        $sql = "SELECT SUM(accumulate_work_time) AS total
11098
                FROM $table
11099
                WHERE c_id = $courseId";
11100
        $result = Database::query($sql);
11101
        $row = Database::fetch_array($result);
11102
11103
        return (int) $row['total'];
11104
    }
11105
11106
    /**
11107
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
11108
     * and put the images in.
11109
     *
11110
     * @return array
11111
     */
11112
    public static function getIconSelect()
11113
    {
11114
        $theme = api_get_visual_theme();
11115
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
11116
        $icons = ['' => get_lang('Please select an option')];
11117
11118
        if (is_dir($path)) {
11119
            $finder = new Finder();
11120
            $finder->files()->in($path);
11121
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
11122
            /** @var SplFileInfo $file */
11123
            foreach ($finder as $file) {
11124
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
11125
                    $icons[$file->getFilename()] = $file->getFilename();
11126
                }
11127
            }
11128
        }
11129
11130
        return $icons;
11131
    }
11132
11133
    /**
11134
     * @param int $lpId
11135
     *
11136
     * @return string
11137
     */
11138
    public static function getSelectedIcon($lpId)
11139
    {
11140
        $extraFieldValue = new ExtraFieldValue('lp');
11141
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
11142
        $icon = '';
11143
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
11144
            $icon = $lpIcon['value'];
11145
        }
11146
11147
        return $icon;
11148
    }
11149
11150
    /**
11151
     * @param int $lpId
11152
     *
11153
     * @return string
11154
     */
11155
    public static function getSelectedIconHtml($lpId)
11156
    {
11157
        $icon = self::getSelectedIcon($lpId);
11158
11159
        if (empty($icon)) {
11160
            return '';
11161
        }
11162
11163
        $theme = api_get_visual_theme();
11164
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
11165
11166
        return Display::img($path);
11167
    }
11168
11169
    /**
11170
     * @param string $value
11171
     *
11172
     * @return string
11173
     */
11174
    public function cleanItemTitle($value)
11175
    {
11176
        $value = Security::remove_XSS(strip_tags($value));
11177
11178
        return $value;
11179
    }
11180
11181
    public function setItemTitle(FormValidator $form)
11182
    {
11183
        if (api_get_configuration_value('save_titles_as_html')) {
11184
            $form->addHtmlEditor(
11185
                'title',
11186
                get_lang('Title'),
11187
                true,
11188
                false,
11189
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
11190
            );
11191
        } else {
11192
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
11193
            $form->applyFilter('title', 'trim');
11194
            $form->applyFilter('title', 'html_filter');
11195
        }
11196
    }
11197
11198
    /**
11199
     * @return array
11200
     */
11201
    public function getItemsForForm($addParentCondition = false)
11202
    {
11203
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11204
11205
        $sql = "SELECT * FROM $tbl_lp_item
11206
                WHERE path <> 'root' AND lp_id = ".$this->lp_id;
11207
11208
        if ($addParentCondition) {
11209
            $sql .= ' AND parent_item_id IS NULL ';
11210
        }
11211
        $sql .= ' ORDER BY display_order ASC';
11212
11213
        $result = Database::query($sql);
11214
        $arrLP = [];
11215
        while ($row = Database::fetch_array($result)) {
11216
            $arrLP[] = [
11217
                'iid' => $row['iid'],
11218
                'id' => $row['iid'],
11219
                'item_type' => $row['item_type'],
11220
                'title' => $this->cleanItemTitle($row['title']),
11221
                'title_raw' => $row['title'],
11222
                'path' => $row['path'],
11223
                'description' => Security::remove_XSS($row['description']),
11224
                'parent_item_id' => $row['parent_item_id'],
11225
                'previous_item_id' => $row['previous_item_id'],
11226
                'next_item_id' => $row['next_item_id'],
11227
                'display_order' => $row['display_order'],
11228
                'max_score' => $row['max_score'],
11229
                'min_score' => $row['min_score'],
11230
                'mastery_score' => $row['mastery_score'],
11231
                'prerequisite' => $row['prerequisite'],
11232
                'max_time_allowed' => $row['max_time_allowed'],
11233
                'prerequisite_min_score' => $row['prerequisite_min_score'],
11234
                'prerequisite_max_score' => $row['prerequisite_max_score'],
11235
            ];
11236
        }
11237
11238
        return $arrLP;
11239
    }
11240
11241
    /**
11242
     * Gets whether this SCORM learning path has been marked to use the score
11243
     * as progress. Takes into account whether the learnpath matches (SCORM
11244
     * content + less than 2 items).
11245
     *
11246
     * @return bool True if the score should be used as progress, false otherwise
11247
     */
11248
    public function getUseScoreAsProgress()
11249
    {
11250
        // If not a SCORM, we don't care about the setting
11251
        if (2 != $this->get_type()) {
11252
            return false;
11253
        }
11254
        // If more than one step in the SCORM, we don't care about the setting
11255
        if ($this->get_total_items_count() > 1) {
11256
            return false;
11257
        }
11258
        $extraFieldValue = new ExtraFieldValue('lp');
11259
        $doUseScore = false;
11260
        $useScore = $extraFieldValue->get_values_by_handler_and_field_variable(
11261
            $this->get_id(),
11262
            'use_score_as_progress'
11263
        );
11264
        if (!empty($useScore) && isset($useScore['value'])) {
11265
            $doUseScore = $useScore['value'];
11266
        }
11267
11268
        return $doUseScore;
11269
    }
11270
11271
    /**
11272
     * Get the user identifier (user_id or username
11273
     * Depends on scorm_api_username_as_student_id in app/config/configuration.php.
11274
     *
11275
     * @return string User ID or username, depending on configuration setting
11276
     */
11277
    public static function getUserIdentifierForExternalServices()
11278
    {
11279
        if (api_get_configuration_value('scorm_api_username_as_student_id')) {
11280
            return api_get_user_info(api_get_user_id())['username'];
11281
        } elseif (null != api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id')) {
11282
            $extraFieldValue = new ExtraFieldValue('user');
11283
            $extrafield = $extraFieldValue->get_values_by_handler_and_field_variable(
11284
                api_get_user_id(),
11285
                api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id')
11286
            );
11287
11288
            return $extrafield['value'];
11289
        } else {
11290
            return api_get_user_id();
11291
        }
11292
    }
11293
11294
    /**
11295
     * Save the new order for learning path items.
11296
     *
11297
     * We have to update parent_item_id, previous_item_id, next_item_id, display_order in the database.
11298
     *
11299
     * @param array $orderList A associative array with item ID as key and parent ID as value.
11300
     */
11301
    public function sortItemByOrderList(array $orderList = [])
11302
    {
11303
        if (empty($orderList)) {
11304
            return true;
11305
        }
11306
        $lpItemRepo = Container::getLpItemRepository();
11307
        $rootParent = $lpItemRepo->getItemRoot($this->get_id());
11308
11309
        /*$previous = 2;
11310
        $next = 0;
11311
        $rootParent->setPreviousItemId(1);
11312
        $last = $this->updateList($orderList, $rootParent, $previous, $next);
11313
        $rootParent->setNextItemId($last + 1);
11314
*/
11315
        $em = Database::getManager();
11316
        //echo '<pre>';
11317
        //var_dump($orderList);
11318
//        $em->persist($rootParent);
11319
11320
        /*$node = new \Tree\Node\Node($rootParent->getIid());
11321
        $parentList[$rootParent->getIid()] = $node;
11322
11323
        foreach ($orderList as $item) {
11324
            $itemId = $item->id ?? 0;
11325
            if (empty($itemId)) {
11326
                continue;
11327
            }
11328
            $parentId = $item->parent_id ?? 0;
11329
11330
            if (empty($parentId)) {
11331
                $parent = $rootParent;
11332
                $parentId = $rootParent->getIid();
11333
            } else {
11334
                //$parent = $lpItemRepo->find($parentId);
11335
            }
11336
            //$parentList[$itemId] = ;
11337
11338
            $child = new \Tree\Node\Node($itemId);
11339
            $parentList[$itemId] = $child;
11340
            $parentList[$parentId]->addChild($child);
11341
        }
11342
11343
        $builder = new Tree\Builder\NodeBuilder;
11344
        echo '<pre>';
11345
        foreach ($node->getChildren() as $child) {
11346
            var_dump($child->getValue().'-'.$child->getDepth().'-'.$child->getSize());
11347
11348
            var_dump("---");
11349
            foreach ($child->getNeighbors() as $neighbor) {
11350
11351
                var_dump($neighbor->getValue(), $child->getSize(), $neighbor->getDepth());
11352
            }
11353
        }
11354
exit;*/
11355
11356
        $counter = 2;
11357
        /*$rootParent->setPreviousItemId(1);
11358
        $rootParent->setDisplayOrder(0);
11359
        $rootParent->setLaunchData(1);*/
11360
        //echo '<pre>';
11361
        $rootParent->setDisplayOrder(1);
11362
        foreach ($orderList as $item) {
11363
            $itemId  = $item->id ?? 0;
11364
            if (empty($itemId)) {
11365
                continue;
11366
            }
11367
            $parentId = $item->parent_id ?? 0;
11368
            $parent = $rootParent;
11369
            if (!empty($parentId)) {
11370
                $parentExists = $lpItemRepo->find($parentId);
11371
                if (null !== $parentExists) {
11372
                    $parent = $parentExists;
11373
                }
11374
            }
11375
11376
            if (isset($parentOrder[$parent->getIid()])) {
11377
                $parentOrder[$parent->getIid()]++;
11378
            } else {
11379
                $parentOrder[$parent->getIid()] = 0;
11380
            }
11381
            /** @var CLpItem $item */
11382
            $item = $lpItemRepo->find($itemId);
11383
11384
            $previousId = 2;
11385
            if (isset($previous[$counter - 1])) {
11386
                $previousId = $previous[$counter - 1];
11387
            }
11388
            $previous[$counter] = $counter + 2;
11389
11390
            $item->setParent($parent);
11391
            //$item->setPreviousItemId($counter);
11392
            //$item->setNextItemId($counter + 1);
11393
            //$item->setDisplayOrder($parentOrder[$parent->getIid()]);
11394
            var_dump($item->getIid().'-'.$counter);
0 ignored issues
show
Security Debugging Code introduced by
var_dump($item->getIid() . '-' . $counter) looks like debug code. Are you sure you do not want to remove it?
Loading history...
11395
            //$item->setPreviousItemId(0);
11396
            //$item->setNextItemId(0);
11397
            $item->setDisplayOrder($counter);
11398
            $em->persist($item);
11399
            $counter++;
11400
        }
11401
        //$rootParent->setNextItemId($counter+1);
11402
        $em->persist($rootParent);
11403
        $em->flush();
11404
        var_dump($lpItemRepo->verify());
11405
        $lpItemRepo->reorder($rootParent, 'displayOrder');
11406
11407
        var_dump($lpItemRepo->verify());
11408
        return true;
11409
        /*
11410
        $lpItemRepo->recoverNode($rootParent);
11411
        var_dump($lpItemRepo->verify());
11412
11413
        $em->flush();
11414
        exit;*/
11415
    }
11416
11417
    private static function updateList($list, $parent, &$previous, &$next)
11418
    {
11419
        //echo '<pre>';        var_dump($list);exit;
11420
        $lpItemRepo = Container::getLpItemRepository();
11421
        $em = Database::getManager();
11422
        $counter = 0;
11423
        foreach ($list as $item) {
11424
            if (empty($item) || !isset($item->id)) {
11425
                continue;
11426
            }
11427
            $id = $item->id;
11428
            $children = $item->children;
11429
11430
            /** @var CLpItem $lpItem */
11431
            $lpItem = $lpItemRepo->find($id);
11432
            $lpItem
11433
                ->setParent($parent)
11434
                ->setDisplayOrder($counter)
11435
                ->setPreviousItemId($previous);
11436
            ;
11437
            $previous++;
11438
            $counter++;
11439
            $childExists = false;
11440
            foreach ($children as $child) {
11441
                if (isset($child->id)) {
11442
                    $childExists = true;
11443
                    break;
11444
                }
11445
            }
11446
11447
            if ($childExists) {
11448
                $next = self::updateList($children, $lpItem, $previous, $next);
11449
                $lpItem->setNextItemId($next + 1);
11450
                $previous++;
11451
            } else {
11452
                $next = $previous;
11453
                $previous++;
11454
                $lpItem->setNextItemId($next);
11455
            }
11456
            $em->persist($lpItem);
11457
        }
11458
11459
        return $next;
11460
    }
11461
11462
    /**
11463
     * Get the depth level of LP item.
11464
     *
11465
     * @param array $items
11466
     * @param int   $currentItemId
11467
     *
11468
     * @return int
11469
     */
11470
    private static function get_level_for_item($items, $currentItemId)
11471
    {
11472
        $parentItemId = 0;
11473
        if (isset($items[$currentItemId])) {
11474
            $parentItemId = $items[$currentItemId]->parent;
11475
        }
11476
11477
        if (0 == $parentItemId) {
11478
            return 0;
11479
        }
11480
11481
        return self::get_level_for_item($items, $parentItemId) + 1;
11482
    }
11483
11484
    /**
11485
     * Generate the link for a learnpath category as course tool.
11486
     *
11487
     * @param int $categoryId
11488
     *
11489
     * @return string
11490
     */
11491
    private static function getCategoryLinkForTool($categoryId)
11492
    {
11493
        $categoryId = (int) $categoryId;
11494
        return 'lp/lp_controller.php?'.api_get_cidreq().'&'
11495
            .http_build_query(
11496
                [
11497
                    'action' => 'view_category',
11498
                    'id' => $categoryId,
11499
                ]
11500
            );
11501
    }
11502
11503
    /**
11504
     * Return the scorm item type object with spaces replaced with _
11505
     * The return result is use to build a css classname like scorm_type_$return.
11506
     *
11507
     * @param $in_type
11508
     *
11509
     * @return mixed
11510
     */
11511
    private static function format_scorm_type_item($in_type)
11512
    {
11513
        return str_replace(' ', '_', $in_type);
11514
    }
11515
11516
    /**
11517
     * Check and obtain the lp final item if exist.
11518
     *
11519
     * @return learnpathItem
11520
     */
11521
    private function getFinalItem()
11522
    {
11523
        if (empty($this->items)) {
11524
            return null;
11525
        }
11526
11527
        foreach ($this->items as $item) {
11528
            if ('final_item' !== $item->type) {
11529
                continue;
11530
            }
11531
11532
            return $item;
11533
        }
11534
    }
11535
11536
    /**
11537
     * Get the LP Final Item Template.
11538
     *
11539
     * @return string
11540
     */
11541
    private function getFinalItemTemplate()
11542
    {
11543
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
11544
    }
11545
11546
    /**
11547
     * Get the LP Final Item Url.
11548
     *
11549
     * @return string
11550
     */
11551
    private function getSavedFinalItem()
11552
    {
11553
        $finalItem = $this->getFinalItem();
11554
11555
        $repo = Container::getDocumentRepository();
11556
        /** @var CDocument $document */
11557
        $document = $repo->find($finalItem->path);
11558
11559
        if ($document && $document->getResourceNode()->hasResourceFile()) {
11560
            return $repo->getResourceFileContent($document);
11561
        }
11562
11563
        return '';
11564
    }
11565
}
11566