Passed
Push — master ( 7a7af2...fe97dd )
by Julito
08:17
created

learnpath   F

Complexity

Total Complexity 1416

Size/Duplication

Total Lines 11487
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 5972
c 0
b 0
f 0
dl 0
loc 11487
rs 0.8
wmc 1416

208 Methods

Rating   Name   Duplication   Size   Complexity  
A get_previous_index() 0 16 5
A get_previous_item_id() 0 5 1
F edit_item() 0 212 22
B delete_item() 0 70 6
A delete_children_items() 0 22 4
A get_total_items_count() 0 3 1
A get_first_item_id() 0 8 2
A get_last() 0 10 2
A get_js_lib() 0 8 2
F first() 0 71 20
A get_current_item_id() 0 8 2
A getLastInFirstLevel() 0 13 2
A get_id() 0 7 2
A get_complete_items_count() 0 24 5
A getTotalItemsCountWithoutDirs() 0 11 3
A get_common_index_terms_by_prefix() 0 17 3
A get_course_int_id() 0 3 2
A getLpNameById() 0 8 1
A set_course_int_id() 0 3 1
F add_item() 0 211 16
F __construct() 0 267 39
A close() 0 13 2
F autocomplete_parents() 0 101 17
B edit_item_prereq() 0 40 7
A get_next_item_id() 0 10 3
F add_lp() 0 139 15
A getCourseCode() 0 3 1
C get_navigation_bar() 0 73 10
B get_next_index() 0 23 7
A getEntity() 0 3 1
A getProgressBar() 0 5 1
B update_default_view_mode() 0 33 6
A next() 0 22 5
B get_iv_objectives_array() 0 38 6
A togglePublish() 0 22 4
A getCategory() 0 6 1
D display_edit_item() 0 84 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 46 4
A getCategoryFromCourseIntoSelect() 0 15 4
F get_link() 0 316 51
A switch_attempt_mode() 0 16 4
A getAccumulateScormTime() 0 3 1
F create_document() 0 146 26
D prerequisites_match() 0 69 16
A previous() 0 11 1
A tree_array() 0 4 1
A createCategory() 0 24 1
C get_exercises() 0 131 11
B move_down() 0 54 8
C fixBlockedLinks() 0 64 11
A open() 0 9 1
A set_jslib() 0 15 2
A createForum() 0 18 1
A update_default_scorm_commit() 0 25 4
A getLpList() 0 9 1
A getCategorySessionId() 0 18 3
B getChildrenToc() 0 49 11
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 40 16
A displayItemMenu() 0 91 5
B set_current_item() 0 33 10
A getUserIdentifierForExternalServices() 0 11 3
A getAccumulateWorkTime() 0 3 1
A get_author() 0 7 2
A display_lp_prerequisites_list() 0 34 5
A set_modified_on() 0 10 1
A getHideTableOfContents() 0 3 1
A categoryIsPublished() 0 25 2
A get_type() 0 8 3
A get_maker() 0 7 2
A display_link_form() 0 22 2
A getForum() 0 9 1
A get_progress_bar() 0 12 1
A getExercisesItems() 0 13 3
B getFinalItemForm() 0 86 4
A getSelectedIcon() 0 10 3
B sortItemByOrderList() 0 48 7
A getCountCategories() 0 10 2
F createReadOutText() 0 121 27
A moveDownCategory() 0 11 2
A get_type_static() 0 16 3
A sort_tree_array() 0 12 3
F scormExport() 0 973 114
A getAccumulateWorkTimePrerequisite() 0 13 1
A getSelectedIconHtml() 0 12 2
A get_items_status_list() 0 10 2
A get_theme() 0 7 2
A display_item_form() 0 14 1
A select_previous_item_id() 0 22 1
A getItemsForForm() 0 39 3
A set_seriousgame_mode() 0 23 4
C isBlockedByPrerequisite() 0 68 13
A returnLpItemList() 0 15 2
B get_progress_bar_text() 0 56 11
A get_teacher_toc_buttons() 0 21 4
B getTOCTree() 0 44 8
A getItem() 0 7 3
A getCategoryId() 0 3 1
B create_tree_array() 0 38 11
B get_links() 0 118 7
A get_items_details_as_js() 0 8 2
A get_progress_bar_mode() 0 7 2
B set_terms_by_prefix() 0 68 10
A get_user_id() 0 7 2
A set_use_max_score() 0 12 1
A 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
A getChapterTypes() 0 4 1
C rl_get_resource_name() 0 92 14
A displayNewSectionForm() 0 17 1
A getSavedFinalItem() 0 13 3
B restart() 0 38 6
A cleanItemTitle() 0 5 1
B get_iv_interactions_array() 0 54 8
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 deleteCategory() 0 22 3
A update_display_order() 0 29 5
A getNameNoTags() 0 3 1
B generate_lp_folder() 0 52 7
A getAccumulateWorkTimeTotalCourse() 0 10 1
A display_quiz_form() 0 13 1
A moveUpCategory() 0 11 2
C get_mediaplayer() 0 80 13
A getProgressFromLpList() 0 32 4
C display_item() 0 81 14
B overview() 0 50 9
B move_up() 0 51 8
A get_interactions_count_from_db() 0 16 2
B get_scorm_prereq_string() 0 73 11
F display_item_prerequisites_form() 0 171 20
B print_recursive() 0 40 10
A clear_prerequisites() 0 14 1
C scorm_export_to_pdf() 0 70 12
B isFirstOrLastItem() 0 32 6
C display_move_item() 0 55 12
C getCalculateStars() 0 80 12
B get_scorm_xml_node() 0 19 7
A has_audio() 0 11 3
A set_encoding() 0 19 4
B get_js_dropdown_array() 0 78 6
A getFinalEvaluationItem() 0 12 3
A setItemTitle() 0 14 2
F build_action_menu() 0 193 15
B get_view() 0 46 7
A getCategoryByCourse() 0 8 1
F is_lp_visible_for_student() 0 141 25
A get_flat_ordered_items_list() 0 35 5
A getProgress() 0 23 2
A getCategoryLinkForTool() 0 12 1
A getSubscriptionSettings() 0 14 2
B verify_document_size() 0 25 8
A get_objectives_count_from_db() 0 16 2
B get_documents() 0 110 2
B get_forums() 0 112 9
A edit_document() 0 14 4
D move_item() 0 125 18
A getCurrentAttempt() 0 8 2
A display_forum_form() 0 16 2
A update_scorm_debug() 0 23 4
A getUseScoreAsProgress() 0 18 5
A toggleCategoryVisibility() 0 19 3
C getParentToc() 0 54 13
A getFinalItem() 0 12 4
A displayResources() 0 55 2
A update_reinit() 0 23 4
A getFinalItemTemplate() 0 3 1
D getListArrayToc() 0 67 11
A get_extension() 0 5 1
F rl_get_resource_link_for_learnpath() 0 169 32
A get_view_id() 0 7 2
A getAccumulateWorkTimeTotal() 0 11 1
A getLpFromSession() 0 28 5
C toggleCategoryPublish() 0 103 12
A generate_learning_path_folder() 0 24 3
A getCategories() 0 9 1
B get_attempt_mode() 0 21 9
D getPackageType() 0 90 20
A toggleVisibility() 0 18 3
A get_lp_session_id() 0 7 2
A return_new_tree() 0 34 4
A getIconSelect() 0 19 4
F exportToCourseBuildFormat() 0 199 31
A get_toc() 0 18 2
F categoryIsVisibleForStudent() 0 89 19
A get_level_for_item() 0 11 3
B delete() 0 125 9
A get_student_publications() 0 42 3
A get_name() 0 7 2
A lpHasForum() 0 26 1
A display_student_publication_form() 0 15 1
A getStatusCSSClassName() 0 7 2
B save_item() 0 47 9
F processBuildMenuElements() 0 440 52

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\User;
6
use Chamilo\CoreBundle\Framework\Container;
7
use Chamilo\CoreBundle\Repository\Node\CourseRepository;
8
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
9
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
10
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
11
use Chamilo\CourseBundle\Entity\CDocument;
12
use Chamilo\CourseBundle\Entity\CForumCategory;
13
use Chamilo\CourseBundle\Entity\CLink;
14
use Chamilo\CourseBundle\Entity\CLp;
15
use Chamilo\CourseBundle\Entity\CLpCategory;
16
use Chamilo\CourseBundle\Entity\CLpItem;
17
use Chamilo\CourseBundle\Entity\CLpItemView;
18
use Chamilo\CourseBundle\Entity\CQuiz;
19
use Chamilo\CourseBundle\Entity\CStudentPublication;
20
use Chamilo\CourseBundle\Entity\CTool;
21
use ChamiloSession as Session;
22
use Gedmo\Sortable\Entity\Repository\SortableRepository;
23
use Symfony\Component\Filesystem\Filesystem;
24
use Symfony\Component\Finder\Finder;
25
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
26
27
/**
28
 * Class learnpath
29
 * This class defines the parent attributes and methods for Chamilo learnpaths
30
 * and SCORM learnpaths. It is used by the scorm class.
31
 *
32
 * @todo decouple class
33
 *
34
 * @author  Yannick Warnier <[email protected]>
35
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
36
 */
37
class learnpath
38
{
39
    public const MAX_LP_ITEM_TITLE_LENGTH = 32;
40
    public const STATUS_CSS_CLASS_NAME = [
41
        'not attempted' => 'scorm_not_attempted',
42
        'incomplete' => 'scorm_not_attempted',
43
        'failed' => 'scorm_failed',
44
        'completed' => 'scorm_completed',
45
        'passed' => 'scorm_completed',
46
        'succeeded' => 'scorm_completed',
47
        'browsed' => 'scorm_completed',
48
    ];
49
50
    public $attempt = 0; // The number for the current ID view.
51
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
52
    public $current; // Id of the current item the user is viewing.
53
    public $current_score; // The score of the current item.
54
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
55
    public $current_time_stop; // The time the user closed this resource.
56
    public $default_status = 'not attempted';
57
    public $encoding = 'UTF-8';
58
    public $error = '';
59
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
60
    public $index; // The index of the active learnpath_item in $ordered_items array.
61
    /** @var learnpathItem[] */
62
    public $items = [];
63
    public $last; // item_id of last item viewed in the learning path.
64
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
65
    public $license; // Which license this course has been given - not used yet on 20060522.
66
    public $lp_id; // DB iid for this learnpath.
67
    public $lp_view_id; // DB ID for lp_view
68
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
69
    public $message = '';
70
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
71
    public $name; // Learnpath name (they generally have one).
72
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
73
    public $path = ''; // Path inside the scorm directory (if scorm).
74
    public $theme; // The current theme of the learning path.
75
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
76
    public $accumulateWorkTime; // The min time of learnpath
77
78
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
79
    public $prevent_reinit = 1;
80
81
    // Describes the mode of progress bar display.
82
    public $seriousgame_mode = 0;
83
    public $progress_bar_mode = '%';
84
85
    // Percentage progress as saved in the db.
86
    public $progress_db = 0;
87
    public $proximity; // Wether the content is distant or local or unknown.
88
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
89
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
90
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
91
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
92
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
93
    public $user_id; //ID of the user that is viewing/using the course
94
    public $update_queue = [];
95
    public $scorm_debug = 0;
96
    public $arrMenu = []; // Array for the menu items.
97
    public $debug = 0; // Logging level.
98
    public $lp_session_id = 0;
99
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
100
    public $prerequisite = 0;
101
    public $use_max_score = 1; // 1 or 0
102
    public $subscribeUsers = 0; // Subscribe users or not
103
    public $created_on = '';
104
    public $modified_on = '';
105
    public $publicated_on = '';
106
    public $expired_on = '';
107
    public $ref = null;
108
    public $course_int_id;
109
    public $course_info = [];
110
    public $categoryId;
111
    public $entity;
112
113
    /**
114
     * Constructor.
115
     * Needs a database handler, a course code and a learnpath id from the database.
116
     * Also builds the list of items into $this->items.
117
     *
118
     * @param string $course  Course code
119
     * @param int    $lp_id   c_lp.iid
120
     * @param int    $user_id
121
     */
122
    public function __construct($course, $lp_id, $user_id)
123
    {
124
        $debug = $this->debug;
125
        $this->encoding = api_get_system_encoding();
126
        if (empty($course)) {
127
            $course = api_get_course_id();
128
        }
129
        $course_info = api_get_course_info($course);
130
        if (!empty($course_info)) {
131
            $this->cc = $course_info['code'];
132
            $this->course_info = $course_info;
133
            $course_id = $course_info['real_id'];
134
        } else {
135
            $this->error = 'Course code does not exist in database.';
136
        }
137
138
        $lp_id = (int) $lp_id;
139
        $course_id = (int) $course_id;
140
        $this->set_course_int_id($course_id);
141
        // Check learnpath ID.
142
        if (empty($lp_id) || empty($course_id)) {
143
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
144
        } else {
145
            $repo = Container::getLpRepository();
146
            /** @var CLp $entity */
147
            $entity = $repo->find($lp_id);
148
            if ($entity) {
0 ignored issues
show
introduced by
$entity is of type Chamilo\CourseBundle\Entity\CLp, thus it always evaluated to true.
Loading history...
149
                $this->entity = $entity;
150
                $this->lp_id = $lp_id;
151
                $this->type = $entity->getLpType();
152
                $this->name = stripslashes($entity->getName());
153
                $this->proximity = $entity->getContentLocal();
154
                $this->theme = $entity->getTheme();
155
                $this->maker = $entity->getContentLocal();
156
                $this->prevent_reinit = $entity->getPreventReinit();
157
                $this->seriousgame_mode = $entity->getSeriousgameMode();
158
                $this->license = $entity->getContentLicense();
159
                $this->scorm_debug = $entity->getDebug();
160
                $this->js_lib = $entity->getJsLib();
161
                $this->path = $entity->getPath();
162
                $this->author = $entity->getAuthor();
163
                $this->hide_toc_frame = $entity->getHideTocFrame();
164
                $this->lp_session_id = $entity->getSessionId();
165
                $this->use_max_score = $entity->getUseMaxScore();
166
                $this->subscribeUsers = $entity->getSubscribeUsers();
167
                $this->created_on = $entity->getCreatedOn()->format('Y-m-d H:i:s');
168
                $this->modified_on = $entity->getModifiedOn()->format('Y-m-d H:i:s');
169
                $this->ref = $entity->getRef();
170
                $this->categoryId = 0;
171
                if ($entity->getCategory()) {
172
                    $this->categoryId = $entity->getCategory()->getIid();
173
                }
174
175
                $this->accumulateScormTime = $entity->getAccumulateWorkTime();
176
177
                if (!empty($entity->getPublicatedOn())) {
178
                    $this->publicated_on = $entity->getPublicatedOn()->format('Y-m-d H:i:s');
179
                }
180
181
                if (!empty($entity->getExpiredOn())) {
182
                    $this->expired_on = $entity->getExpiredOn()->format('Y-m-d H:i:s');
183
                }
184
                if (2 == $this->type) {
185
                    if (1 == $entity->getForceCommit()) {
186
                        $this->force_commit = true;
187
                    }
188
                }
189
                $this->mode = $entity->getDefaultViewMod();
190
191
                // Check user ID.
192
                if (empty($user_id)) {
193
                    $this->error = 'User ID is empty';
194
                } else {
195
                    $userInfo = api_get_user_info($user_id);
196
                    if (!empty($userInfo)) {
197
                        $this->user_id = $userInfo['user_id'];
198
                    } else {
199
                        $this->error = 'User ID does not exist in database #'.$user_id;
200
                    }
201
                }
202
203
                // End of variables checking.
204
                $session_id = api_get_session_id();
205
                //  Get the session condition for learning paths of the base + session.
206
                $session = api_get_session_condition($session_id);
207
                // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
208
                $lp_table = Database::get_course_table(TABLE_LP_VIEW);
209
210
                // Selecting by view_count descending allows to get the highest view_count first.
211
                $sql = "SELECT * FROM $lp_table
212
                        WHERE
213
                            c_id = $course_id AND
214
                            lp_id = $lp_id AND
215
                            user_id = $user_id
216
                            $session
217
                        ORDER BY view_count DESC";
218
                $res = Database::query($sql);
219
220
                if (Database::num_rows($res) > 0) {
221
                    $row = Database::fetch_array($res);
222
                    $this->attempt = $row['view_count'];
223
                    $this->lp_view_id = $row['iid'];
224
                    $this->last_item_seen = $row['last_item'];
225
                    $this->progress_db = $row['progress'];
226
                    $this->lp_view_session_id = $row['session_id'];
227
                } elseif (!api_is_invitee()) {
228
                    $this->attempt = 1;
229
                    $params = [
230
                        'c_id' => $course_id,
231
                        'lp_id' => $lp_id,
232
                        'user_id' => $user_id,
233
                        'view_count' => 1,
234
                        'session_id' => $session_id,
235
                        'last_item' => 0,
236
                    ];
237
                    $this->last_item_seen = 0;
238
                    $this->lp_view_session_id = $session_id;
239
                    $this->lp_view_id = Database::insert($lp_table, $params);
240
                }
241
242
                // Initialise items.
243
                $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
244
                $sql = "SELECT * FROM $lp_item_table
245
                        WHERE c_id = $course_id AND lp_id = '".$this->lp_id."'
246
                        ORDER BY parent_item_id, display_order";
247
                $res = Database::query($sql);
248
249
                $lp_item_id_list = [];
250
                while ($row = Database::fetch_array($res)) {
251
                    $lp_item_id_list[] = $row['iid'];
252
                    switch ($this->type) {
253
                        case 3: //aicc
254
                            $oItem = new aiccItem('db', $row['iid'], $course_id);
255
                            if (is_object($oItem)) {
256
                                $my_item_id = $oItem->get_id();
257
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
258
                                $oItem->set_prevent_reinit($this->prevent_reinit);
259
                                // Don't use reference here as the next loop will make the pointed object change.
260
                                $this->items[$my_item_id] = $oItem;
261
                                $this->refs_list[$oItem->ref] = $my_item_id;
262
                            }
263
                            break;
264
                        case 2:
265
                            $oItem = new scormItem('db', $row['iid'], $course_id);
266
                            if (is_object($oItem)) {
267
                                $my_item_id = $oItem->get_id();
268
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
269
                                $oItem->set_prevent_reinit($this->prevent_reinit);
270
                                // Don't use reference here as the next loop will make the pointed object change.
271
                                $this->items[$my_item_id] = $oItem;
272
                                $this->refs_list[$oItem->ref] = $my_item_id;
273
                            }
274
                            break;
275
                        case 1:
276
                        default:
277
                            $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
278
                            if (is_object($oItem)) {
279
                                $my_item_id = $oItem->get_id();
280
                                // Moved down to when we are sure the item_view exists.
281
                                //$oItem->set_lp_view($this->lp_view_id);
282
                                $oItem->set_prevent_reinit($this->prevent_reinit);
283
                                // Don't use reference here as the next loop will make the pointed object change.
284
                                $this->items[$my_item_id] = $oItem;
285
                                $this->refs_list[$my_item_id] = $my_item_id;
286
                            }
287
                            break;
288
                    }
289
290
                    // Setting the object level with variable $this->items[$i][parent]
291
                    foreach ($this->items as $itemLPObject) {
292
                        $level = self::get_level_for_item(
293
                            $this->items,
294
                            $itemLPObject->db_id
295
                        );
296
                        $itemLPObject->level = $level;
297
                    }
298
299
                    // Setting the view in the item object.
300
                    if (is_object($this->items[$row['iid']])) {
301
                        $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
302
                        if (TOOL_HOTPOTATOES == $this->items[$row['iid']]->get_type()) {
303
                            $this->items[$row['iid']]->current_start_time = 0;
304
                            $this->items[$row['iid']]->current_stop_time = 0;
305
                        }
306
                    }
307
                }
308
309
                if (!empty($lp_item_id_list)) {
310
                    $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
311
                    if (!empty($lp_item_id_list_to_string)) {
312
                        // Get last viewing vars.
313
                        $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
314
                        // This query should only return one or zero result.
315
                        $sql = "SELECT lp_item_id, status
316
                                FROM $itemViewTable
317
                                WHERE
318
                                    c_id = $course_id AND
319
                                    lp_view_id = ".$this->get_view_id()." AND
320
                                    lp_item_id IN ('".$lp_item_id_list_to_string."')
321
                                ORDER BY view_count DESC ";
322
                        $status_list = [];
323
                        $res = Database::query($sql);
324
                        while ($row = Database:: fetch_array($res)) {
325
                            $status_list[$row['lp_item_id']] = $row['status'];
326
                        }
327
328
                        foreach ($lp_item_id_list as $item_id) {
329
                            if (isset($status_list[$item_id])) {
330
                                $status = $status_list[$item_id];
331
                                if (is_object($this->items[$item_id])) {
332
                                    $this->items[$item_id]->set_status($status);
333
                                    if (empty($status)) {
334
                                        $this->items[$item_id]->set_status(
335
                                            $this->default_status
336
                                        );
337
                                    }
338
                                }
339
                            } else {
340
                                if (!api_is_invitee()) {
341
                                    if (is_object($this->items[$item_id])) {
342
                                        $this->items[$item_id]->set_status(
343
                                            $this->default_status
344
                                        );
345
                                    }
346
347
                                    if (!empty($this->lp_view_id)) {
348
                                        // Add that row to the lp_item_view table so that
349
                                        // we have something to show in the stats page.
350
                                        $params = [
351
                                            'c_id' => $course_id,
352
                                            'lp_item_id' => $item_id,
353
                                            'lp_view_id' => $this->lp_view_id,
354
                                            'view_count' => 1,
355
                                            'status' => 'not attempted',
356
                                            'start_time' => time(),
357
                                            'total_time' => 0,
358
                                            'score' => 0,
359
                                        ];
360
                                        Database::insert($itemViewTable, $params);
361
362
                                        $this->items[$item_id]->set_lp_view(
363
                                            $this->lp_view_id,
364
                                            $course_id
365
                                        );
366
                                    }
367
                                }
368
                            }
369
                        }
370
                    }
371
                }
372
373
                $this->ordered_items = self::get_flat_ordered_items_list(
374
                    $this->get_id(),
375
                    0,
376
                    $course_id
377
                );
378
                $this->max_ordered_items = 0;
379
                foreach ($this->ordered_items as $index => $dummy) {
380
                    if ($index > $this->max_ordered_items && !empty($dummy)) {
381
                        $this->max_ordered_items = $index;
382
                    }
383
                }
384
                // TODO: Define the current item better.
385
                $this->first();
386
                if ($debug) {
387
                    error_log('lp_view_session_id '.$this->lp_view_session_id);
388
                    error_log('End of learnpath constructor for learnpath '.$this->get_id());
389
                }
390
            }
391
        }
392
    }
393
394
    public function getEntity(): CLp
395
    {
396
        return $this->entity;
397
    }
398
399
    /**
400
     * @return string
401
     */
402
    public function getCourseCode()
403
    {
404
        return $this->cc;
405
    }
406
407
    /**
408
     * @return int
409
     */
410
    public function get_course_int_id()
411
    {
412
        return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
413
    }
414
415
    /**
416
     * @param $course_id
417
     *
418
     * @return int
419
     */
420
    public function set_course_int_id($course_id)
421
    {
422
        return $this->course_int_id = (int) $course_id;
423
    }
424
425
    /**
426
     * Function rewritten based on old_add_item() from Yannick Warnier.
427
     * Due the fact that users can decide where the item should come, I had to overlook this function and
428
     * I found it better to rewrite it. Old function is still available.
429
     * Added also the possibility to add a description.
430
     *
431
     * @param int    $parent
432
     * @param int    $previous
433
     * @param string $type
434
     * @param int    $id               resource ID (ref)
435
     * @param string $title
436
     * @param string $description
437
     * @param int    $prerequisites
438
     * @param int    $max_time_allowed
439
     * @param int    $userId
440
     *
441
     * @return int
442
     */
443
    public function add_item(
444
        $parent,
445
        $previous,
446
        $type = 'dir',
447
        $id,
448
        $title,
449
        $description,
450
        $prerequisites = 0,
451
        $max_time_allowed = 0,
452
        $userId = 0
453
    ) {
454
        $course_id = $this->course_info['real_id'];
455
        if (empty($course_id)) {
456
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
457
            $this->course_info = api_get_course_info($this->cc);
458
            $course_id = $this->course_info['real_id'];
459
        }
460
        $userId = empty($userId) ? api_get_user_id() : $userId;
461
        $sessionId = api_get_session_id();
462
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
463
        $_course = $this->course_info;
464
        $parent = (int) $parent;
465
        $previous = (int) $previous;
466
        $id = (int) $id;
467
        $max_time_allowed = (int) $max_time_allowed;
468
        if (empty($max_time_allowed)) {
469
            $max_time_allowed = 0;
470
        }
471
        $sql = "SELECT COUNT(iid) AS num
472
                FROM $tbl_lp_item
473
                WHERE
474
                    c_id = $course_id AND
475
                    lp_id = ".$this->get_id()." AND
476
                    parent_item_id = $parent ";
477
478
        $res_count = Database::query($sql);
479
        $row = Database::fetch_array($res_count);
480
        $num = $row['num'];
481
482
        $tmp_previous = 0;
483
        $display_order = 0;
484
        $next = 0;
485
        if ($num > 0) {
486
            if (empty($previous)) {
487
                $sql = "SELECT iid, next_item_id, display_order
488
                        FROM $tbl_lp_item
489
                        WHERE
490
                            c_id = $course_id AND
491
                            lp_id = ".$this->get_id()." AND
492
                            parent_item_id = $parent AND
493
                            previous_item_id = 0 OR
494
                            previous_item_id = $parent ";
495
                $result = Database::query($sql);
496
                $row = Database::fetch_array($result);
497
                if ($row) {
498
                    $next = $row['iid'];
499
                }
500
            } else {
501
                $previous = (int) $previous;
502
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
503
						FROM $tbl_lp_item
504
                        WHERE
505
                            c_id = $course_id AND
506
                            lp_id = ".$this->get_id()." AND
507
                            iid = $previous";
508
                $result = Database::query($sql);
509
                $row = Database::fetch_array($result);
510
                if ($row) {
511
                    $tmp_previous = $row['iid'];
512
                    $next = $row['next_item_id'];
513
                    $display_order = $row['display_order'];
514
                }
515
            }
516
        }
517
518
        $id = (int) $id;
519
        $typeCleaned = Database::escape_string($type);
520
        $max_score = 100;
521
        if ('quiz' === $type && $id) {
522
            $sql = 'SELECT SUM(ponderation)
523
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
524
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
525
                    ON
526
                        quiz_question.id = quiz_rel_question.question_id AND
527
                        quiz_question.c_id = quiz_rel_question.c_id
528
                    WHERE
529
                        quiz_rel_question.exercice_id = '.$id." AND
530
                        quiz_question.c_id = $course_id AND
531
                        quiz_rel_question.c_id = $course_id ";
532
            $rsQuiz = Database::query($sql);
533
            $max_score = Database::result($rsQuiz, 0, 0);
534
535
            // Disabling the exercise if we add it inside a LP
536
            $exercise = new Exercise($course_id);
537
            $exercise->read($id);
538
            $exercise->disable();
539
            $exercise->save();
540
        }
541
542
        $params = [
543
            'c_id' => $course_id,
544
            'lp_id' => $this->get_id(),
545
            'item_type' => $typeCleaned,
546
            'ref' => '',
547
            'title' => $title,
548
            'description' => $description,
549
            'path' => $id,
550
            'max_score' => $max_score,
551
            'parent_item_id' => $parent,
552
            'previous_item_id' => $previous,
553
            'next_item_id' => (int) $next,
554
            'display_order' => $display_order + 1,
555
            'prerequisite' => $prerequisites,
556
            'max_time_allowed' => $max_time_allowed,
557
            'min_score' => 0,
558
            'launch_data' => '',
559
        ];
560
561
        if (0 != $prerequisites) {
562
            $params['prerequisite'] = $prerequisites;
563
        }
564
565
        $new_item_id = Database::insert($tbl_lp_item, $params);
566
        if ($new_item_id) {
567
            if (!empty($next)) {
568
                $sql = "UPDATE $tbl_lp_item
569
                        SET previous_item_id = $new_item_id
570
                        WHERE c_id = $course_id AND iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
571
                Database::query($sql);
572
            }
573
574
            // Update the item that should be before the new item.
575
            if (!empty($tmp_previous)) {
576
                $sql = "UPDATE $tbl_lp_item
577
                        SET next_item_id = $new_item_id
578
                        WHERE c_id = $course_id AND iid = $tmp_previous";
579
                Database::query($sql);
580
            }
581
582
            // Update all the items after the new item.
583
            $sql = "UPDATE $tbl_lp_item
584
                        SET display_order = display_order + 1
585
                    WHERE
586
                        c_id = $course_id AND
587
                        lp_id = ".$this->get_id()." AND
588
                        iid <> $new_item_id AND
589
                        parent_item_id = $parent AND
590
                        display_order > $display_order";
591
            Database::query($sql);
592
593
            // Update the item that should come after the new item.
594
            $sql = "UPDATE $tbl_lp_item
595
                    SET ref = $new_item_id
596
                    WHERE c_id = $course_id AND iid = $new_item_id";
597
            Database::query($sql);
598
599
            $sql = "UPDATE $tbl_lp_item
600
                    SET previous_item_id = ".$this->getLastInFirstLevel()."
601
                    WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
602
            Database::query($sql);
603
604
            // Upload audio.
605
            if (!empty($_FILES['mp3']['name'])) {
606
                // Create the audio folder if it does not exist yet.
607
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
608
                if (!is_dir($filepath.'audio')) {
609
                    mkdir(
610
                        $filepath.'audio',
611
                        api_get_permissions_for_new_directories()
612
                    );
613
                    $audio_id = DocumentManager::addDocument(
614
                        $_course,
615
                        '/audio',
616
                        'folder',
617
                        0,
618
                        'audio',
619
                        '',
620
                        0,
621
                        true,
622
                        null,
623
                        $sessionId,
624
                        $userId
625
                    );
626
                }
627
628
                $file_path = handle_uploaded_document(
629
                    $_course,
630
                    $_FILES['mp3'],
631
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
632
                    '/audio',
633
                    $userId,
634
                    '',
635
                    '',
636
                    '',
637
                    '',
638
                    false
639
                );
640
641
                // Getting the filename only.
642
                $file_components = explode('/', $file_path);
643
                $file = $file_components[count($file_components) - 1];
644
645
                // Store the mp3 file in the lp_item table.
646
                $sql = "UPDATE $tbl_lp_item SET
647
                          audio = '".Database::escape_string($file)."'
648
                        WHERE iid = '".intval($new_item_id)."'";
649
                Database::query($sql);
650
            }
651
        }
652
653
        return $new_item_id;
654
    }
655
656
    /**
657
     * Static admin function allowing addition of a learnpath to a course.
658
     *
659
     * @param string $courseCode
660
     * @param string $name
661
     * @param string $description
662
     * @param string $learnpath
663
     * @param string $origin
664
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
665
     * @param string $publicated_on
666
     * @param string $expired_on
667
     * @param int    $categoryId
668
     * @param int    $userId
669
     *
670
     * @return CLp
671
     */
672
    public static function add_lp(
673
        $courseCode,
674
        $name,
675
        $description = '',
676
        $learnpath = 'guess',
677
        $origin = 'zip',
678
        $zipname = '',
679
        $publicated_on = '',
680
        $expired_on = '',
681
        $categoryId = 0,
682
        $userId = 0
683
    ) {
684
        global $charset;
685
686
        if (!empty($courseCode)) {
687
            $courseInfo = api_get_course_info($courseCode);
688
            $course_id = $courseInfo['real_id'];
689
        } else {
690
            $course_id = api_get_course_int_id();
691
            $courseInfo = api_get_course_info();
692
        }
693
694
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
695
        // Check course code exists.
696
        // Check lp_name doesn't exist, otherwise append something.
697
        $i = 0;
698
        $categoryId = (int) $categoryId;
699
        // Session id.
700
        $session_id = api_get_session_id();
701
        $userId = empty($userId) ? api_get_user_id() : $userId;
702
703
        if (empty($publicated_on)) {
704
            $publicated_on = null;
705
        } else {
706
            $publicated_on = api_get_utc_datetime($publicated_on, true, true);
707
        }
708
709
        if (empty($expired_on)) {
710
            $expired_on = null;
711
        } else {
712
            $expired_on = api_get_utc_datetime($expired_on, true, true);
713
        }
714
715
        $check_name = "SELECT * FROM $tbl_lp
716
                       WHERE c_id = $course_id AND name = '".Database::escape_string($name)."'";
717
        $res_name = Database::query($check_name);
718
719
        while (Database::num_rows($res_name)) {
720
            // There is already one such name, update the current one a bit.
721
            $i++;
722
            $name = $name.' - '.$i;
723
            $check_name = "SELECT * FROM $tbl_lp
724
                           WHERE c_id = $course_id AND name = '".Database::escape_string($name)."' ";
725
            $res_name = Database::query($check_name);
726
        }
727
        // New name does not exist yet; keep it.
728
        // Escape description.
729
        // Kevin: added htmlentities().
730
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
731
        $type = 1;
732
        switch ($learnpath) {
733
            case 'guess':
734
            case 'aicc':
735
                break;
736
            case 'dokeos':
737
            case 'chamilo':
738
                $type = 1;
739
                break;
740
        }
741
742
        $id = null;
743
        $sessionEntity = api_get_session_entity();
744
        $courseEntity = api_get_course_entity($courseInfo['real_id']);
745
746
        switch ($origin) {
747
            case 'zip':
748
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
749
                break;
750
            case 'manual':
751
            default:
752
                $get_max = "SELECT MAX(display_order)
753
                            FROM $tbl_lp WHERE c_id = $course_id";
754
                $res_max = Database::query($get_max);
755
                if (Database::num_rows($res_max) < 1) {
756
                    $dsp = 1;
757
                } else {
758
                    $row = Database::fetch_array($res_max);
759
                    $dsp = $row[0] + 1;
760
                }
761
762
                $category = null;
763
                if (!empty($categoryId)) {
764
                    $category = Container::getLpCategoryRepository()->find($categoryId);
765
                }
766
767
                $lp = new CLp();
768
                $lp
769
                    ->setCId($course_id)
770
                    ->setLpType($type)
771
                    ->setName($name)
772
                    ->setDescription($description)
773
                    ->setDisplayOrder($dsp)
774
                    ->setSessionId($session_id)
775
                    ->setCategory($category)
776
                    ->setPublicatedOn($publicated_on)
777
                    ->setExpiredOn($expired_on)
778
                    ->setParent($courseEntity)
779
                    ->addCourseLink($courseEntity, $sessionEntity)
780
                ;
781
782
                $em = Database::getManager();
783
                $em->persist($lp);
784
                $em->flush();
785
786
                if ($lp->getIid()) {
787
                    $id = $lp->getIid();
788
                }
789
790
                // Insert into item_property.
791
                /*api_item_property_update(
792
                    $courseInfo,
793
                    TOOL_LEARNPATH,
794
                    $id,
795
                    'LearnpathAdded',
796
                    $userId
797
                );
798
                api_set_default_visibility(
799
                    $id,
800
                    TOOL_LEARNPATH,
801
                    0,
802
                    $courseInfo,
803
                    $session_id,
804
                    $userId
805
                );*/
806
807
                break;
808
        }
809
810
        return $lp;
811
    }
812
813
    /**
814
     * Auto completes the parents of an item in case it's been completed or passed.
815
     *
816
     * @param int $item Optional ID of the item from which to look for parents
817
     */
818
    public function autocomplete_parents($item)
819
    {
820
        $debug = $this->debug;
821
822
        if (empty($item)) {
823
            $item = $this->current;
824
        }
825
826
        $currentItem = $this->getItem($item);
827
        if ($currentItem) {
828
            $parent_id = $currentItem->get_parent();
829
            $parent = $this->getItem($parent_id);
830
            if ($parent) {
831
                // if $item points to an object and there is a parent.
832
                if ($debug) {
833
                    error_log(
834
                        'Autocompleting parent of item '.$item.' '.
835
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
836
                        0
837
                    );
838
                }
839
840
                // New experiment including failed and browsed in completed status.
841
                //$current_status = $currentItem->get_status();
842
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
843
                // Fixes chapter auto complete
844
                if (true) {
845
                    // If the current item is completed or passes or succeeded.
846
                    $updateParentStatus = true;
847
                    if ($debug) {
848
                        error_log('Status of current item is alright');
849
                    }
850
851
                    foreach ($parent->get_children() as $childItemId) {
852
                        $childItem = $this->getItem($childItemId);
853
854
                        // If children was not set try to get the info
855
                        if (empty($childItem->db_item_view_id)) {
856
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
857
                        }
858
859
                        // Check all his brothers (parent's children) for completion status.
860
                        if ($childItemId != $item) {
861
                            if ($debug) {
862
                                error_log(
863
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
864
                                    0
865
                                );
866
                            }
867
                            // Trying completing parents of failed and browsed items as well.
868
                            if ($childItem->status_is(
869
                                [
870
                                    'completed',
871
                                    'passed',
872
                                    'succeeded',
873
                                    'browsed',
874
                                    'failed',
875
                                ]
876
                            )
877
                            ) {
878
                                // Keep completion status to true.
879
                                continue;
880
                            } else {
881
                                if ($debug > 2) {
882
                                    error_log(
883
                                        '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,
884
                                        0
885
                                    );
886
                                }
887
                                $updateParentStatus = false;
888
                                break;
889
                            }
890
                        }
891
                    }
892
893
                    if ($updateParentStatus) {
894
                        // If all the children were completed:
895
                        $parent->set_status('completed');
896
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
897
                        // Force the status to "completed"
898
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
899
                        $this->update_queue[$parent->get_id()] = 'completed';
900
                        if ($debug) {
901
                            error_log(
902
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
903
                                print_r($this->update_queue, 1),
904
                                0
905
                            );
906
                        }
907
                        // Recursive call.
908
                        $this->autocomplete_parents($parent->get_id());
909
                    }
910
                }
911
            } else {
912
                if ($debug) {
913
                    error_log("Parent #$parent_id does not exists");
914
                }
915
            }
916
        } else {
917
            if ($debug) {
918
                error_log("#$item is an item that doesn't have parents");
919
            }
920
        }
921
    }
922
923
    /**
924
     * Closes the current resource.
925
     *
926
     * Stops the timer
927
     * Saves into the database if required
928
     * Clears the current resource data from this object
929
     *
930
     * @return bool True on success, false on failure
931
     */
932
    public function close()
933
    {
934
        if (empty($this->lp_id)) {
935
            $this->error = 'Trying to close this learnpath but no ID is set';
936
937
            return false;
938
        }
939
        $this->current_time_stop = time();
940
        $this->ordered_items = [];
941
        $this->index = 0;
942
        unset($this->lp_id);
943
        //unset other stuff
944
        return true;
945
    }
946
947
    /**
948
     * Static admin function allowing removal of a learnpath.
949
     *
950
     * @param array  $courseInfo
951
     * @param int    $id         Learnpath ID
952
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
953
     *
954
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
955
     */
956
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
957
    {
958
        $course_id = api_get_course_int_id();
959
        if (!empty($courseInfo)) {
960
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
961
        }
962
963
        // TODO: Implement a way of getting this to work when the current object is not set.
964
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
965
        // If an ID is specifically given and the current LP is not the same, prevent delete.
966
        if (!empty($id) && ($id != $this->lp_id)) {
967
            return false;
968
        }
969
970
        $lp = Database::get_course_table(TABLE_LP_MAIN);
971
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
972
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
973
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
974
975
        // Delete lp item id.
976
        foreach ($this->items as $lpItemId => $dummy) {
977
            $sql = "DELETE FROM $lp_item_view
978
                    WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
979
            Database::query($sql);
980
        }
981
982
        // Proposed by Christophe (nickname: clefevre)
983
        $sql = "DELETE FROM $lp_item
984
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
985
        Database::query($sql);
986
987
        $sql = "DELETE FROM $lp_view
988
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
989
        Database::query($sql);
990
991
        self::toggleVisibility($this->lp_id, 0);
992
993
        /*if (2 == $this->type || 3 == $this->type) {
994
            // This is a scorm learning path, delete the files as well.
995
            $sql = "SELECT path FROM $lp
996
                    WHERE iid = ".$this->lp_id;
997
            $res = Database::query($sql);
998
            if (Database::num_rows($res) > 0) {
999
                $row = Database::fetch_array($res);
1000
                $path = $row['path'];
1001
                $sql = "SELECT iid FROM $lp
1002
                        WHERE
1003
                            c_id = $course_id AND
1004
                            path = '$path' AND
1005
                            iid != ".$this->lp_id;
1006
                $res = Database::query($sql);
1007
                if (Database::num_rows($res) > 0) {
1008
                    // Another learning path uses this directory, so don't delete it.
1009
                    if ($this->debug > 2) {
1010
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1011
                    }
1012
                } else {
1013
                    // No other LP uses that directory, delete it.
1014
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1015
                    // The absolute system path for this course.
1016
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
1017
                    if ('remove' == $delete && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1018
                        if ($this->debug > 2) {
1019
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1020
                        }
1021
                        // Proposed by Christophe (clefevre).
1022
                        if (0 == strcmp(substr($path, -2), "/.")) {
1023
                            $path = substr($path, 0, -1); // Remove "." at the end.
1024
                        }
1025
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1026
                        rmdirr($course_scorm_dir.$path);
1027
                    }
1028
                }
1029
            }
1030
        }*/
1031
1032
       if (api_get_configuration_value('allow_lp_subscription_to_usergroups')) {
1033
            $table = Database::get_course_table(TABLE_LP_REL_USERGROUP);
1034
            $sql = "DELETE FROM $table
1035
                    WHERE
1036
                        lp_id = {$this->lp_id} AND
1037
                        c_id = $course_id ";
1038
            Database::query($sql);
1039
        }
1040
1041
        /*$tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1042
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1043
        // Delete tools
1044
        $sql = "DELETE FROM $tbl_tool
1045
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1046
        Database::query($sql);*/
1047
1048
        /*$sql = "DELETE FROM $lp
1049
                WHERE iid = ".$this->lp_id;
1050
        Database::query($sql);*/
1051
        $repo = Container::getLpRepository();
1052
        $lp = $repo->find($this->lp_id);
1053
        Database::getManager()->remove($lp);
1054
        Database::getManager()->flush();
1055
1056
        // Updates the display order of all lps.
1057
        $this->update_display_order();
1058
1059
        /*api_item_property_update(
1060
            api_get_course_info(),
1061
            TOOL_LEARNPATH,
1062
            $this->lp_id,
1063
            'delete',
1064
            api_get_user_id()
1065
        );*/
1066
1067
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1068
            api_get_course_id(),
1069
            4,
1070
            $id,
1071
            api_get_session_id()
1072
        );
1073
1074
        if (false !== $link_info) {
1075
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1076
        }
1077
1078
        if ('true' == api_get_setting('search_enabled')) {
1079
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1080
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1081
        }
1082
    }
1083
1084
    /**
1085
     * Removes all the children of one item - dangerous!
1086
     *
1087
     * @param int $id Element ID of which children have to be removed
1088
     *
1089
     * @return int Total number of children removed
1090
     */
1091
    public function delete_children_items($id)
1092
    {
1093
        $course_id = $this->course_info['real_id'];
1094
1095
        $num = 0;
1096
        $id = (int) $id;
1097
        if (empty($id) || empty($course_id)) {
1098
            return false;
1099
        }
1100
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1101
        $sql = "SELECT * FROM $lp_item
1102
                WHERE c_id = $course_id AND parent_item_id = $id";
1103
        $res = Database::query($sql);
1104
        while ($row = Database::fetch_array($res)) {
1105
            $num += $this->delete_children_items($row['iid']);
1106
            $sql = "DELETE FROM $lp_item
1107
                    WHERE c_id = $course_id AND iid = ".$row['iid'];
1108
            Database::query($sql);
1109
            $num++;
1110
        }
1111
1112
        return $num;
1113
    }
1114
1115
    /**
1116
     * Removes an item from the current learnpath.
1117
     *
1118
     * @param int $id Elem ID (0 if first)
1119
     *
1120
     * @return int Number of elements moved
1121
     *
1122
     * @todo implement resource removal
1123
     */
1124
    public function delete_item($id)
1125
    {
1126
        $course_id = api_get_course_int_id();
1127
        $id = (int) $id;
1128
        // TODO: Implement the resource removal.
1129
        if (empty($id) || empty($course_id)) {
1130
            return false;
1131
        }
1132
        // First select item to get previous, next, and display order.
1133
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1134
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1135
        $res_sel = Database::query($sql_sel);
1136
        if (Database::num_rows($res_sel) < 1) {
1137
            return false;
1138
        }
1139
        $row = Database::fetch_array($res_sel);
1140
        $previous = $row['previous_item_id'];
1141
        $next = $row['next_item_id'];
1142
        $display = $row['display_order'];
1143
        $parent = $row['parent_item_id'];
1144
        $lp = $row['lp_id'];
1145
        // Delete children items.
1146
        $this->delete_children_items($id);
1147
        // Now delete the item.
1148
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1149
        Database::query($sql_del);
1150
        // Now update surrounding items.
1151
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1152
                    WHERE iid = $previous";
1153
        Database::query($sql_upd);
1154
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1155
                    WHERE iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
1156
        Database::query($sql_upd);
1157
        // Now update all following items with new display order.
1158
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1159
                    WHERE
1160
                        c_id = $course_id AND
1161
                        lp_id = $lp AND
1162
                        parent_item_id = $parent AND
1163
                        display_order > $display";
1164
        Database::query($sql_all);
1165
1166
        //Removing prerequisites since the item will not longer exist
1167
        $sql_all = "UPDATE $lp_item SET prerequisite = ''
1168
                    WHERE c_id = $course_id AND prerequisite = '$id'";
1169
        Database::query($sql_all);
1170
1171
        $sql = "UPDATE $lp_item
1172
                SET previous_item_id = ".$this->getLastInFirstLevel()."
1173
                WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
1174
        Database::query($sql);
1175
1176
        // Remove from search engine if enabled.
1177
        if ('true' === api_get_setting('search_enabled')) {
1178
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1179
            $sql = 'SELECT * FROM %s
1180
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1181
                    LIMIT 1';
1182
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1183
            $res = Database::query($sql);
1184
            if (Database::num_rows($res) > 0) {
1185
                $row2 = Database::fetch_array($res);
1186
                $di = new ChamiloIndexer();
1187
                $di->remove_document($row2['search_did']);
1188
            }
1189
            $sql = 'DELETE FROM %s
1190
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1191
                    LIMIT 1';
1192
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1193
            Database::query($sql);
1194
        }
1195
    }
1196
1197
    /**
1198
     * Updates an item's content in place.
1199
     *
1200
     * @param int    $id               Element ID
1201
     * @param int    $parent           Parent item ID
1202
     * @param int    $previous         Previous item ID
1203
     * @param string $title            Item title
1204
     * @param string $description      Item description
1205
     * @param string $prerequisites    Prerequisites (optional)
1206
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1207
     * @param int    $max_time_allowed
1208
     * @param string $url
1209
     *
1210
     * @return bool True on success, false on error
1211
     */
1212
    public function edit_item(
1213
        $id,
1214
        $parent,
1215
        $previous,
1216
        $title,
1217
        $description,
1218
        $prerequisites = '0',
1219
        $audio = [],
1220
        $max_time_allowed = 0,
1221
        $url = ''
1222
    ) {
1223
        $course_id = api_get_course_int_id();
1224
        $_course = api_get_course_info();
1225
        $id = (int) $id;
1226
1227
        if (empty($max_time_allowed)) {
1228
            $max_time_allowed = 0;
1229
        }
1230
1231
        if (empty($id) || empty($_course)) {
1232
            return false;
1233
        }
1234
1235
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1236
        $sql = "SELECT * FROM $tbl_lp_item
1237
                WHERE iid = $id";
1238
        $res_select = Database::query($sql);
1239
        $row_select = Database::fetch_array($res_select);
1240
        $audio_update_sql = '';
1241
        if (is_array($audio) && !empty($audio['tmp_name']) && 0 === $audio['error']) {
1242
            // Create the audio folder if it does not exist yet.
1243
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1244
            if (!is_dir($filepath.'audio')) {
1245
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1246
                $audio_id = DocumentManager::addDocument(
1247
                    $_course,
1248
                    '/audio',
1249
                    'folder',
1250
                    0,
1251
                    'audio'
1252
                );
1253
            }
1254
1255
            // Upload file in documents.
1256
            $pi = pathinfo($audio['name']);
1257
            if ('mp3' === $pi['extension']) {
1258
                $c_det = api_get_course_info($this->cc);
1259
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1260
                $path = handle_uploaded_document(
1261
                    $c_det,
1262
                    $audio,
1263
                    $bp,
1264
                    '/audio',
1265
                    api_get_user_id(),
1266
                    0,
1267
                    null,
1268
                    0,
1269
                    'rename',
1270
                    false,
1271
                    0
1272
                );
1273
                $path = substr($path, 7);
1274
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1275
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1276
            }
1277
        }
1278
1279
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1280
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1281
1282
        // TODO: htmlspecialchars to be checked for encoding related problems.
1283
        if ($same_parent && $same_previous) {
1284
            // Only update title and description.
1285
            $sql = "UPDATE $tbl_lp_item
1286
                    SET title = '".Database::escape_string($title)."',
1287
                        prerequisite = '".$prerequisites."',
1288
                        description = '".Database::escape_string($description)."'
1289
                        ".$audio_update_sql.",
1290
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1291
                    WHERE iid = $id";
1292
            Database::query($sql);
1293
        } else {
1294
            $old_parent = $row_select['parent_item_id'];
1295
            $old_previous = $row_select['previous_item_id'];
1296
            $old_next = $row_select['next_item_id'];
1297
            $old_order = $row_select['display_order'];
1298
            $old_prerequisite = $row_select['prerequisite'];
1299
            $old_max_time_allowed = $row_select['max_time_allowed'];
1300
1301
            /* BEGIN -- virtually remove the current item id */
1302
            /* for the next and previous item it is like the current item doesn't exist anymore */
1303
            if (0 != $old_previous) {
1304
                // Next
1305
                $sql = "UPDATE $tbl_lp_item
1306
                        SET next_item_id = $old_next
1307
                        WHERE iid = $old_previous";
1308
                Database::query($sql);
1309
            }
1310
1311
            if (!empty($old_next)) {
1312
                // Previous
1313
                $sql = "UPDATE $tbl_lp_item
1314
                        SET previous_item_id = $old_previous
1315
                        WHERE iid = $old_next";
1316
                Database::query($sql);
1317
            }
1318
1319
            // display_order - 1 for every item with a display_order
1320
            // bigger then the display_order of the current item.
1321
            $sql = "UPDATE $tbl_lp_item
1322
                    SET display_order = display_order - 1
1323
                    WHERE
1324
                        c_id = $course_id AND
1325
                        display_order > $old_order AND
1326
                        lp_id = ".$this->lp_id." AND
1327
                        parent_item_id = $old_parent";
1328
            Database::query($sql);
1329
            /* END -- virtually remove the current item id */
1330
1331
            /* BEGIN -- update the current item id to his new location */
1332
            if (0 == $previous) {
1333
                // Select the data of the item that should come after the current item.
1334
                $sql = "SELECT id, display_order
1335
                        FROM $tbl_lp_item
1336
                        WHERE
1337
                            c_id = $course_id AND
1338
                            lp_id = ".$this->lp_id." AND
1339
                            parent_item_id = $parent AND
1340
                            previous_item_id = $previous";
1341
                $res_select_old = Database::query($sql);
1342
                $row_select_old = Database::fetch_array($res_select_old);
1343
1344
                // If the new parent didn't have children before.
1345
                if (0 == Database::num_rows($res_select_old)) {
1346
                    $new_next = 0;
1347
                    $new_order = 1;
1348
                } else {
1349
                    $new_next = $row_select_old['id'];
1350
                    $new_order = $row_select_old['display_order'];
1351
                }
1352
            } else {
1353
                // Select the data of the item that should come before the current item.
1354
                $sql = "SELECT next_item_id, display_order
1355
                        FROM $tbl_lp_item
1356
                        WHERE iid = $previous";
1357
                $res_select_old = Database::query($sql);
1358
                $row_select_old = Database::fetch_array($res_select_old);
1359
                $new_next = $row_select_old['next_item_id'];
1360
                $new_order = $row_select_old['display_order'] + 1;
1361
            }
1362
1363
            // TODO: htmlspecialchars to be checked for encoding related problems.
1364
            // Update the current item with the new data.
1365
            $sql = "UPDATE $tbl_lp_item
1366
                    SET
1367
                        title = '".Database::escape_string($title)."',
1368
                        description = '".Database::escape_string($description)."',
1369
                        parent_item_id = $parent,
1370
                        previous_item_id = $previous,
1371
                        next_item_id = $new_next,
1372
                        display_order = $new_order
1373
                        $audio_update_sql
1374
                    WHERE iid = $id";
1375
            Database::query($sql);
1376
1377
            if (0 != $previous) {
1378
                // Update the previous item's next_item_id.
1379
                $sql = "UPDATE $tbl_lp_item
1380
                        SET next_item_id = $id
1381
                        WHERE iid = $previous";
1382
                Database::query($sql);
1383
            }
1384
1385
            if (!empty($new_next)) {
1386
                // Update the next item's previous_item_id.
1387
                $sql = "UPDATE $tbl_lp_item
1388
                        SET previous_item_id = $id
1389
                        WHERE iid = $new_next";
1390
                Database::query($sql);
1391
            }
1392
1393
            if ($old_prerequisite != $prerequisites) {
1394
                $sql = "UPDATE $tbl_lp_item
1395
                        SET prerequisite = '$prerequisites'
1396
                        WHERE iid = $id";
1397
                Database::query($sql);
1398
            }
1399
1400
            if ($old_max_time_allowed != $max_time_allowed) {
1401
                // update max time allowed
1402
                $sql = "UPDATE $tbl_lp_item
1403
                        SET max_time_allowed = $max_time_allowed
1404
                        WHERE iid = $id";
1405
                Database::query($sql);
1406
            }
1407
1408
            // Update all the items with the same or a bigger display_order than the current item.
1409
            $sql = "UPDATE $tbl_lp_item
1410
                    SET display_order = display_order + 1
1411
                    WHERE
1412
                       c_id = $course_id AND
1413
                       lp_id = ".$this->get_id()." AND
1414
                       iid <> $id AND
1415
                       parent_item_id = $parent AND
1416
                       display_order >= $new_order";
1417
            Database::query($sql);
1418
        }
1419
1420
        if ('link' == $row_select['item_type']) {
1421
            $link = new Link();
1422
            $linkId = $row_select['path'];
1423
            $link->updateLink($linkId, $url);
1424
        }
1425
    }
1426
1427
    /**
1428
     * Updates an item's prereq in place.
1429
     *
1430
     * @param int    $id              Element ID
1431
     * @param string $prerequisite_id Prerequisite Element ID
1432
     * @param int    $minScore        Prerequisite min score
1433
     * @param int    $maxScore        Prerequisite max score
1434
     *
1435
     * @return bool True on success, false on error
1436
     */
1437
    public function edit_item_prereq(
1438
        $id,
1439
        $prerequisite_id,
1440
        $minScore = 0,
1441
        $maxScore = 100
1442
    ) {
1443
        $id = (int) $id;
1444
1445
        if (empty($id)) {
1446
            return false;
1447
        }
1448
        $prerequisite_id = (int) $prerequisite_id;
1449
1450
        if (empty($minScore) || $minScore < 0) {
1451
            $minScore = 0;
1452
        }
1453
1454
        if (empty($maxScore) || $maxScore < 0) {
1455
            $maxScore = 100;
1456
        }
1457
1458
        $minScore = (float) $minScore;
1459
        $maxScore = (float) $maxScore;
1460
1461
        if (empty($prerequisite_id)) {
1462
            $prerequisite_id = 'NULL';
1463
            $minScore = 0;
1464
            $maxScore = 100;
1465
        }
1466
1467
        $table = Database::get_course_table(TABLE_LP_ITEM);
1468
        $sql = " UPDATE $table
1469
                 SET
1470
                    prerequisite = $prerequisite_id ,
1471
                    prerequisite_min_score = $minScore ,
1472
                    prerequisite_max_score = $maxScore
1473
                 WHERE iid = $id";
1474
        Database::query($sql);
1475
1476
        return true;
1477
    }
1478
1479
    /**
1480
     * Get the specific prefix index terms of this learning path.
1481
     *
1482
     * @param string $prefix
1483
     *
1484
     * @return array Array of terms
1485
     */
1486
    public function get_common_index_terms_by_prefix($prefix)
1487
    {
1488
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1489
        $terms = get_specific_field_values_list_by_prefix(
1490
            $prefix,
1491
            $this->cc,
1492
            TOOL_LEARNPATH,
1493
            $this->lp_id
1494
        );
1495
        $prefix_terms = [];
1496
        if (!empty($terms)) {
1497
            foreach ($terms as $term) {
1498
                $prefix_terms[] = $term['value'];
1499
            }
1500
        }
1501
1502
        return $prefix_terms;
1503
    }
1504
1505
    /**
1506
     * Gets the number of items currently completed.
1507
     *
1508
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1509
     *
1510
     * @return int The number of items currently completed
1511
     */
1512
    public function get_complete_items_count($failedStatusException = false)
1513
    {
1514
        $i = 0;
1515
        $completedStatusList = [
1516
            'completed',
1517
            'passed',
1518
            'succeeded',
1519
            'browsed',
1520
        ];
1521
1522
        if (!$failedStatusException) {
1523
            $completedStatusList[] = 'failed';
1524
        }
1525
1526
        foreach ($this->items as $id => $dummy) {
1527
            // Trying failed and browsed considered "progressed" as well.
1528
            if ($this->items[$id]->status_is($completedStatusList) &&
1529
                'dir' != $this->items[$id]->get_type()
1530
            ) {
1531
                $i++;
1532
            }
1533
        }
1534
1535
        return $i;
1536
    }
1537
1538
    /**
1539
     * Gets the current item ID.
1540
     *
1541
     * @return int The current learnpath item id
1542
     */
1543
    public function get_current_item_id()
1544
    {
1545
        $current = 0;
1546
        if (!empty($this->current)) {
1547
            $current = (int) $this->current;
1548
        }
1549
1550
        return $current;
1551
    }
1552
1553
    /**
1554
     * Force to get the first learnpath item id.
1555
     *
1556
     * @return int The current learnpath item id
1557
     */
1558
    public function get_first_item_id()
1559
    {
1560
        $current = 0;
1561
        if (is_array($this->ordered_items)) {
1562
            $current = $this->ordered_items[0];
1563
        }
1564
1565
        return $current;
1566
    }
1567
1568
    /**
1569
     * Gets the total number of items available for viewing in this SCORM.
1570
     *
1571
     * @return int The total number of items
1572
     */
1573
    public function get_total_items_count()
1574
    {
1575
        return count($this->items);
1576
    }
1577
1578
    /**
1579
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1580
     *
1581
     * @return int The total no-chapters number of items
1582
     */
1583
    public function getTotalItemsCountWithoutDirs()
1584
    {
1585
        $total = 0;
1586
        $typeListNotToCount = self::getChapterTypes();
1587
        foreach ($this->items as $temp2) {
1588
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1589
                $total++;
1590
            }
1591
        }
1592
1593
        return $total;
1594
    }
1595
1596
    /**
1597
     *  Sets the first element URL.
1598
     */
1599
    public function first()
1600
    {
1601
        if ($this->debug > 0) {
1602
            error_log('In learnpath::first()', 0);
1603
            error_log('$this->last_item_seen '.$this->last_item_seen);
1604
        }
1605
1606
        // Test if the last_item_seen exists and is not a dir.
1607
        if (0 == count($this->ordered_items)) {
1608
            $this->index = 0;
1609
        }
1610
1611
        if (!empty($this->last_item_seen) &&
1612
            !empty($this->items[$this->last_item_seen]) &&
1613
            'dir' != $this->items[$this->last_item_seen]->get_type()
1614
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1615
            //&& !$this->items[$this->last_item_seen]->is_done()
1616
        ) {
1617
            if ($this->debug > 2) {
1618
                error_log(
1619
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1620
                    $this->items[$this->last_item_seen]->get_type()
1621
                );
1622
            }
1623
            $index = -1;
1624
            foreach ($this->ordered_items as $myindex => $item_id) {
1625
                if ($item_id == $this->last_item_seen) {
1626
                    $index = $myindex;
1627
                    break;
1628
                }
1629
            }
1630
            if (-1 == $index) {
1631
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1632
                if ($this->debug > 2) {
1633
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1634
                }
1635
1636
                return false;
1637
            } else {
1638
                $this->last = $this->last_item_seen;
1639
                $this->current = $this->last_item_seen;
1640
                $this->index = $index;
1641
            }
1642
        } else {
1643
            if ($this->debug > 2) {
1644
                error_log('In learnpath::first() - No last item seen', 0);
1645
            }
1646
            $index = 0;
1647
            // Loop through all ordered items and stop at the first item that is
1648
            // not a directory *and* that has not been completed yet.
1649
            while (!empty($this->ordered_items[$index]) &&
1650
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1651
                (
1652
                    'dir' == $this->items[$this->ordered_items[$index]]->get_type() ||
1653
                    true === $this->items[$this->ordered_items[$index]]->is_done()
1654
                ) && $index < $this->max_ordered_items) {
1655
                $index++;
1656
            }
1657
1658
            $this->last = $this->current;
1659
            // current is
1660
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1661
            $this->index = $index;
1662
            if ($this->debug > 2) {
1663
                error_log('$index '.$index);
1664
                error_log('In learnpath::first() - No last item seen');
1665
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1666
            }
1667
        }
1668
        if ($this->debug > 2) {
1669
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1670
        }
1671
    }
1672
1673
    /**
1674
     * Gets the js library from the database.
1675
     *
1676
     * @return string The name of the javascript library to be used
1677
     */
1678
    public function get_js_lib()
1679
    {
1680
        $lib = '';
1681
        if (!empty($this->js_lib)) {
1682
            $lib = $this->js_lib;
1683
        }
1684
1685
        return $lib;
1686
    }
1687
1688
    /**
1689
     * Gets the learnpath database ID.
1690
     *
1691
     * @return int Learnpath ID in the lp table
1692
     */
1693
    public function get_id()
1694
    {
1695
        if (!empty($this->lp_id)) {
1696
            return (int) $this->lp_id;
1697
        }
1698
1699
        return 0;
1700
    }
1701
1702
    /**
1703
     * Gets the last element URL.
1704
     *
1705
     * @return string URL to load into the viewer
1706
     */
1707
    public function get_last()
1708
    {
1709
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1710
        if (count($this->ordered_items) > 0) {
1711
            $this->index = count($this->ordered_items) - 1;
1712
1713
            return $this->ordered_items[$this->index];
1714
        }
1715
1716
        return false;
1717
    }
1718
1719
    /**
1720
     * Get the last element in the first level.
1721
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1722
     *
1723
     * @return mixed
1724
     */
1725
    public function getLastInFirstLevel()
1726
    {
1727
        try {
1728
            $lastId = Database::getManager()
1729
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1730
                WHERE i.lpId = :lp AND i.parentItemId = 0 AND i.itemType != :type ORDER BY i.displayOrder DESC')
1731
                ->setMaxResults(1)
1732
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1733
                ->getSingleScalarResult();
1734
1735
            return $lastId;
1736
        } catch (Exception $exception) {
1737
            return 0;
1738
        }
1739
    }
1740
1741
    /**
1742
     * Get the learning path name by id.
1743
     *
1744
     * @param int $lpId
1745
     *
1746
     * @return mixed
1747
     */
1748
    public static function getLpNameById($lpId)
1749
    {
1750
        $em = Database::getManager();
1751
1752
        return $em->createQuery('SELECT clp.name FROM ChamiloCourseBundle:CLp clp
1753
            WHERE clp.iid = :iid')
1754
            ->setParameter('iid', $lpId)
1755
            ->getSingleScalarResult();
1756
    }
1757
1758
    /**
1759
     * Gets the navigation bar for the learnpath display screen.
1760
     *
1761
     * @param string $barId
1762
     *
1763
     * @return string The HTML string to use as a navigation bar
1764
     */
1765
    public function get_navigation_bar($barId = '')
1766
    {
1767
        if (empty($barId)) {
1768
            $barId = 'control-top';
1769
        }
1770
        $lpId = $this->lp_id;
1771
        $mycurrentitemid = $this->get_current_item_id();
1772
1773
        $reportingText = get_lang('Reporting');
1774
        $previousText = get_lang('Previous');
1775
        $nextText = get_lang('Next');
1776
        $fullScreenText = get_lang('Back to normal screen');
1777
1778
        $settings = api_get_configuration_value('lp_view_settings');
1779
        $display = isset($settings['display']) ? $settings['display'] : false;
1780
        $reportingIcon = '
1781
            <a class="icon-toolbar"
1782
                id="stats_link"
1783
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
1784
                onclick="window.parent.API.save_asset(); return true;"
1785
                target="content_name" title="'.$reportingText.'">
1786
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1787
            </a>';
1788
1789
        if (!empty($display)) {
1790
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1791
            if (false === $showReporting) {
1792
                $reportingIcon = '';
1793
            }
1794
        }
1795
1796
        $hideArrows = false;
1797
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1798
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1799
        }
1800
1801
        $previousIcon = '';
1802
        $nextIcon = '';
1803
        if (false === $hideArrows) {
1804
            $previousIcon = '
1805
                <a class="icon-toolbar" id="scorm-previous" href="#"
1806
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1807
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1808
                </a>';
1809
1810
            $nextIcon = '
1811
                <a class="icon-toolbar" id="scorm-next" href="#"
1812
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1813
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1814
                </a>';
1815
        }
1816
1817
        if ('fullscreen' === $this->mode) {
1818
            $navbar = '
1819
                  <span id="'.$barId.'" class="buttons">
1820
                    '.$reportingIcon.'
1821
                    '.$previousIcon.'
1822
                    '.$nextIcon.'
1823
                    <a class="icon-toolbar" id="view-embedded"
1824
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1825
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1826
                    </a>
1827
                  </span>';
1828
        } else {
1829
            $navbar = '
1830
                 <span id="'.$barId.'" class="buttons text-right">
1831
                    '.$reportingIcon.'
1832
                    '.$previousIcon.'
1833
                    '.$nextIcon.'
1834
                </span>';
1835
        }
1836
1837
        return $navbar;
1838
    }
1839
1840
    /**
1841
     * Gets the next resource in queue (url).
1842
     *
1843
     * @return string URL to load into the viewer
1844
     */
1845
    public function get_next_index()
1846
    {
1847
        // TODO
1848
        $index = $this->index;
1849
        $index++;
1850
        while (
1851
            !empty($this->ordered_items[$index]) && ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) &&
1852
            $index < $this->max_ordered_items
1853
        ) {
1854
            $index++;
1855
            if ($index == $this->max_ordered_items) {
1856
                if ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) {
1857
                    return $this->index;
1858
                }
1859
1860
                return $index;
1861
            }
1862
        }
1863
        if (empty($this->ordered_items[$index])) {
1864
            return $this->index;
1865
        }
1866
1867
        return $index;
1868
    }
1869
1870
    /**
1871
     * Gets item_id for the next element.
1872
     *
1873
     * @return int Next item (DB) ID
1874
     */
1875
    public function get_next_item_id()
1876
    {
1877
        $new_index = $this->get_next_index();
1878
        if (!empty($new_index)) {
1879
            if (isset($this->ordered_items[$new_index])) {
1880
                return $this->ordered_items[$new_index];
1881
            }
1882
        }
1883
1884
        return 0;
1885
    }
1886
1887
    /**
1888
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
1889
     *
1890
     * Generally, the package provided is in the form of a zip file, so the function
1891
     * has been written to test a zip file. If not a zip, the function will return the
1892
     * default return value: ''
1893
     *
1894
     * @param string $file_path the path to the file
1895
     * @param string $file_name the original name of the file
1896
     *
1897
     * @return string 'scorm','aicc','scorm2004','dokeos', 'error-empty-package'
1898
     * if the package is empty, or '' if the package cannot be recognized
1899
     */
1900
    public static function getPackageType($file_path, $file_name)
1901
    {
1902
        // Get name of the zip file without the extension.
1903
        $file_info = pathinfo($file_name);
1904
        $extension = $file_info['extension']; // Extension only.
1905
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
1906
                'dll',
1907
                'exe',
1908
            ])) {
1909
            return 'oogie';
1910
        }
1911
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
1912
                'dll',
1913
                'exe',
1914
            ])) {
1915
            return 'woogie';
1916
        }
1917
1918
        $zipFile = new PclZip($file_path);
1919
        // Check the zip content (real size and file extension).
1920
        $zipContentArray = $zipFile->listContent();
1921
        $package_type = '';
1922
        $manifest = '';
1923
        $aicc_match_crs = 0;
1924
        $aicc_match_au = 0;
1925
        $aicc_match_des = 0;
1926
        $aicc_match_cst = 0;
1927
        $countItems = 0;
1928
1929
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
1930
        if (is_array($zipContentArray)) {
1931
            $countItems = count($zipContentArray);
1932
            if ($countItems > 0) {
1933
                foreach ($zipContentArray as $thisContent) {
1934
                    if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
1935
                        // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
1936
                    } elseif (false !== stristr($thisContent['filename'], 'imsmanifest.xml')) {
1937
                        $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
1938
                        $package_type = 'scorm';
1939
                        break; // Exit the foreach loop.
1940
                    } elseif (
1941
                        preg_match('/aicc\//i', $thisContent['filename']) ||
1942
                        in_array(
1943
                            strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
1944
                            ['crs', 'au', 'des', 'cst']
1945
                        )
1946
                    ) {
1947
                        $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
1948
                        switch ($ext) {
1949
                            case 'crs':
1950
                                $aicc_match_crs = 1;
1951
                                break;
1952
                            case 'au':
1953
                                $aicc_match_au = 1;
1954
                                break;
1955
                            case 'des':
1956
                                $aicc_match_des = 1;
1957
                                break;
1958
                            case 'cst':
1959
                                $aicc_match_cst = 1;
1960
                                break;
1961
                            default:
1962
                                break;
1963
                        }
1964
                        //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
1965
                    } else {
1966
                        $package_type = '';
1967
                    }
1968
                }
1969
            }
1970
        }
1971
1972
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
1973
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
1974
            $package_type = 'aicc';
1975
        }
1976
1977
        // Try with chamilo course builder
1978
        if (empty($package_type)) {
1979
            // Sometimes users will try to upload an empty zip, or a zip with
1980
            // only a folder. Catch that and make the calling function aware.
1981
            // If the single file was the imsmanifest.xml, then $package_type
1982
            // would be 'scorm' and we wouldn't be here.
1983
            if ($countItems < 2) {
1984
                return 'error-empty-package';
1985
            }
1986
            $package_type = 'chamilo';
1987
        }
1988
1989
        return $package_type;
1990
    }
1991
1992
    /**
1993
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
1994
     *
1995
     * @return string URL to load into the viewer
1996
     */
1997
    public function get_previous_index()
1998
    {
1999
        $index = $this->index;
2000
        if (isset($this->ordered_items[$index - 1])) {
2001
            $index--;
2002
            while (isset($this->ordered_items[$index]) &&
2003
                ('dir' == $this->items[$this->ordered_items[$index]]->get_type())
2004
            ) {
2005
                $index--;
2006
                if ($index < 0) {
2007
                    return $this->index;
2008
                }
2009
            }
2010
        }
2011
2012
        return $index;
2013
    }
2014
2015
    /**
2016
     * Gets item_id for the next element.
2017
     *
2018
     * @return int Previous item (DB) ID
2019
     */
2020
    public function get_previous_item_id()
2021
    {
2022
        $index = $this->get_previous_index();
2023
2024
        return $this->ordered_items[$index];
2025
    }
2026
2027
    /**
2028
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2029
     *
2030
     * @param int    $lpItemId
2031
     * @param string $autostart
2032
     *
2033
     * @return string The mediaplayer HTML
2034
     */
2035
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2036
    {
2037
        $course_id = api_get_course_int_id();
2038
        $courseInfo = api_get_course_info();
2039
        $lpItemId = (int) $lpItemId;
2040
2041
        if (empty($courseInfo) || empty($lpItemId)) {
2042
            return '';
2043
        }
2044
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
2045
2046
        if (empty($item)) {
2047
            return '';
2048
        }
2049
2050
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2051
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2052
        $itemViewId = (int) $item->db_item_view_id;
2053
2054
        // Getting all the information about the item.
2055
        $sql = "SELECT lp_view.status
2056
                FROM $tbl_lp_item as lpi
2057
                INNER JOIN $tbl_lp_item_view as lp_view
2058
                ON (lpi.iid = lp_view.lp_item_id)
2059
                WHERE
2060
                    lp_view.iid = $itemViewId AND
2061
                    lpi.iid = $lpItemId AND
2062
                    lp_view.c_id = $course_id";
2063
        $result = Database::query($sql);
2064
        $row = Database::fetch_assoc($result);
2065
        $output = '';
2066
        $audio = $item->audio;
2067
2068
        if (!empty($audio)) {
2069
            $list = $_SESSION['oLP']->get_toc();
2070
2071
            switch ($item->get_type()) {
2072
                case 'quiz':
2073
                    $type_quiz = false;
2074
                    foreach ($list as $toc) {
2075
                        if ($toc['id'] == $_SESSION['oLP']->current) {
2076
                            $type_quiz = true;
2077
                        }
2078
                    }
2079
2080
                    if ($type_quiz) {
2081
                        if (1 == $_SESSION['oLP']->prevent_reinit) {
2082
                            $autostart_audio = 'completed' === $row['status'] ? 'false' : 'true';
2083
                        } else {
2084
                            $autostart_audio = $autostart;
2085
                        }
2086
                    }
2087
                    break;
2088
                case TOOL_READOUT_TEXT:
2089
                    $autostart_audio = 'false';
2090
                    break;
2091
                default:
2092
                    $autostart_audio = 'true';
2093
            }
2094
2095
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'.$audio;
2096
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document'.$audio.'?'.api_get_cidreq();
2097
2098
            $player = Display::getMediaPlayer(
2099
                $file,
2100
                [
2101
                    'id' => 'lp_audio_media_player',
2102
                    'url' => $url,
2103
                    'autoplay' => $autostart_audio,
2104
                    'width' => '100%',
2105
                ]
2106
            );
2107
2108
            // The mp3 player.
2109
            $output = '<div id="container">';
2110
            $output .= $player;
2111
            $output .= '</div>';
2112
        }
2113
2114
        return $output;
2115
    }
2116
2117
    /**
2118
     * @param int   $studentId
2119
     * @param int   $prerequisite
2120
     * @param array $courseInfo
2121
     * @param int   $sessionId
2122
     *
2123
     * @return bool
2124
     */
2125
    public static function isBlockedByPrerequisite(
2126
        $studentId,
2127
        $prerequisite,
2128
        $courseInfo,
2129
        $sessionId
2130
    ) {
2131
        if (empty($courseInfo)) {
2132
            return false;
2133
        }
2134
2135
        $courseId = $courseInfo['real_id'];
2136
2137
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2138
        if ($allow) {
2139
            if (api_is_allowed_to_edit() ||
2140
                api_is_platform_admin(true) ||
2141
                api_is_drh() ||
2142
                api_is_coach($sessionId, $courseId, false)
2143
            ) {
2144
                return false;
2145
            }
2146
        }
2147
2148
        $isBlocked = false;
2149
        if (!empty($prerequisite)) {
2150
            $progress = self::getProgress(
2151
                $prerequisite,
2152
                $studentId,
2153
                $courseId,
2154
                $sessionId
2155
            );
2156
            if ($progress < 100) {
2157
                $isBlocked = true;
2158
            }
2159
2160
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2161
                // Block if it does not exceed minimum time
2162
                // Minimum time (in minutes) to pass the learning path
2163
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
2164
2165
                if ($accumulateWorkTime > 0) {
2166
                    // Total time in course (sum of times in learning paths from course)
2167
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
2168
2169
                    // Connect with the plugin_licences_course_session table
2170
                    // which indicates what percentage of the time applies
2171
                    // Minimum connection percentage
2172
                    $perc = 100;
2173
                    // Time from the course
2174
                    $tc = $accumulateWorkTimeTotal;
2175
2176
                    // Percentage of the learning paths
2177
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
2178
                    // Minimum time for each learning path
2179
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
2180
2181
                    // Spent time (in seconds) so far in the learning path
2182
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
2183
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
2184
2185
                    if ($lpTime < ($accumulateWorkTime * 60)) {
2186
                        $isBlocked = true;
2187
                    }
2188
                }
2189
            }
2190
        }
2191
2192
        return $isBlocked;
2193
    }
2194
2195
    /**
2196
     * Checks if the learning path is visible for student after the progress
2197
     * of its prerequisite is completed, considering the time availability and
2198
     * the LP visibility.
2199
     *
2200
     * @param int   $student_id
2201
     * @param array $courseInfo
2202
     * @param int   $sessionId
2203
     *
2204
     * @return bool
2205
     */
2206
    public static function is_lp_visible_for_student(
2207
        CLp $lp,
2208
        $student_id,
2209
        $courseInfo = [],
2210
        $sessionId = 0
2211
    ) {
2212
        $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
2213
        $sessionId = (int) $sessionId;
2214
2215
        if (empty($courseInfo)) {
2216
            return false;
2217
        }
2218
2219
        if (empty($sessionId)) {
2220
            $sessionId = api_get_session_id();
2221
        }
2222
2223
        $courseId = $courseInfo['real_id'];
2224
2225
        /*$itemInfo = api_get_item_property_info(
2226
            $courseId,
2227
            TOOL_LEARNPATH,
2228
            $lp_id,
2229
            $sessionId
2230
        );*/
2231
2232
        $visibility = $lp->isVisible($courseInfo['entity'], api_get_session_entity($sessionId));
2233
        // If the item was deleted.
2234
        if (false === $visibility) {
2235
            return false;
2236
        }
2237
2238
        $lp_id = $lp->getIid();
2239
        // @todo remove this query and load the row info as a parameter
2240
        $table = Database::get_course_table(TABLE_LP_MAIN);
2241
        // Get current prerequisite
2242
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on, category_id
2243
                FROM $table
2244
                WHERE iid = $lp_id";
2245
        $rs = Database::query($sql);
2246
        $now = time();
2247
        if (Database::num_rows($rs) > 0) {
2248
            $row = Database::fetch_array($rs, 'ASSOC');
2249
2250
            if (!empty($row['category_id'])) {
2251
                $em = Database::getManager();
2252
                $category = $em->getRepository('ChamiloCourseBundle:CLpCategory')->find($row['category_id']);
2253
                if (false === self::categoryIsVisibleForStudent($category, api_get_user_entity($student_id))) {
2254
                    return false;
2255
                }
2256
            }
2257
2258
            $prerequisite = $row['prerequisite'];
2259
            $is_visible = true;
2260
2261
            $isBlocked = self::isBlockedByPrerequisite(
2262
                $student_id,
2263
                $prerequisite,
2264
                $courseInfo,
2265
                $sessionId
2266
            );
2267
2268
            if ($isBlocked) {
2269
                $is_visible = false;
2270
            }
2271
2272
            // Also check the time availability of the LP
2273
            if ($is_visible) {
2274
                // Adding visibility restrictions
2275
                if (!empty($row['publicated_on'])) {
2276
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2277
                        $is_visible = false;
2278
                    }
2279
                }
2280
                // Blocking empty start times see BT#2800
2281
                global $_custom;
2282
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2283
                    $_custom['lps_hidden_when_no_start_date']
2284
                ) {
2285
                    if (empty($row['publicated_on'])) {
2286
                        $is_visible = false;
2287
                    }
2288
                }
2289
2290
                if (!empty($row['expired_on'])) {
2291
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2292
                        $is_visible = false;
2293
                    }
2294
                }
2295
            }
2296
2297
            if ($is_visible) {
2298
                $subscriptionSettings = self::getSubscriptionSettings();
2299
2300
                // Check if the subscription users/group to a LP is ON
2301
                if (isset($row['subscribe_users']) && 1 == $row['subscribe_users'] &&
2302
                    true === $subscriptionSettings['allow_add_users_to_lp']
2303
                ) {
2304
                    // Try group
2305
                    $is_visible = false;
2306
                    // Checking only the user visibility
2307
                    $userVisibility = api_get_item_visibility(
2308
                        $courseInfo,
2309
                        'learnpath',
2310
                        $row['id'],
2311
                        $sessionId,
2312
                        $student_id,
2313
                        'LearnpathSubscription'
2314
                    );
2315
2316
                    if (1 == $userVisibility) {
2317
                        $is_visible = true;
2318
                    } else {
2319
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
2320
                        if (!empty($userGroups)) {
2321
                            foreach ($userGroups as $groupInfo) {
2322
                                $groupId = $groupInfo['iid'];
2323
                                $userVisibility = api_get_item_visibility(
2324
                                    $courseInfo,
2325
                                    'learnpath',
2326
                                    $row['id'],
2327
                                    $sessionId,
2328
                                    null,
2329
                                    'LearnpathSubscription',
2330
                                    $groupId
2331
                                );
2332
2333
                                if (1 == $userVisibility) {
2334
                                    $is_visible = true;
2335
                                    break;
2336
                                }
2337
                            }
2338
                        }
2339
                    }
2340
                }
2341
            }
2342
2343
            return $is_visible;
2344
        }
2345
2346
        return false;
2347
    }
2348
2349
    /**
2350
     * @param int $lpId
2351
     * @param int $userId
2352
     * @param int $courseId
2353
     * @param int $sessionId
2354
     *
2355
     * @return int
2356
     */
2357
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2358
    {
2359
        $lpId = (int) $lpId;
2360
        $userId = (int) $userId;
2361
        $courseId = (int) $courseId;
2362
        $sessionId = (int) $sessionId;
2363
2364
        $sessionCondition = api_get_session_condition($sessionId);
2365
        $table = Database::get_course_table(TABLE_LP_VIEW);
2366
        $sql = "SELECT progress FROM $table
2367
                WHERE
2368
                    c_id = $courseId AND
2369
                    lp_id = $lpId AND
2370
                    user_id = $userId $sessionCondition ";
2371
        $res = Database::query($sql);
2372
2373
        $progress = 0;
2374
        if (Database::num_rows($res) > 0) {
2375
            $row = Database::fetch_array($res);
2376
            $progress = (int) $row['progress'];
2377
        }
2378
2379
        return $progress;
2380
    }
2381
2382
    /**
2383
     * @param array $lpList
2384
     * @param int   $userId
2385
     * @param int   $courseId
2386
     * @param int   $sessionId
2387
     *
2388
     * @return array
2389
     */
2390
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2391
    {
2392
        $lpList = array_map('intval', $lpList);
2393
        if (empty($lpList)) {
2394
            return [];
2395
        }
2396
2397
        $lpList = implode("','", $lpList);
2398
2399
        $userId = (int) $userId;
2400
        $courseId = (int) $courseId;
2401
        $sessionId = (int) $sessionId;
2402
2403
        $sessionCondition = api_get_session_condition($sessionId);
2404
        $table = Database::get_course_table(TABLE_LP_VIEW);
2405
        $sql = "SELECT lp_id, progress FROM $table
2406
                WHERE
2407
                    c_id = $courseId AND
2408
                    lp_id IN ('".$lpList."') AND
2409
                    user_id = $userId $sessionCondition ";
2410
        $res = Database::query($sql);
2411
2412
        if (Database::num_rows($res) > 0) {
2413
            $list = [];
2414
            while ($row = Database::fetch_array($res)) {
2415
                $list[$row['lp_id']] = $row['progress'];
2416
            }
2417
2418
            return $list;
2419
        }
2420
2421
        return [];
2422
    }
2423
2424
    /**
2425
     * Displays a progress bar
2426
     * completed so far.
2427
     *
2428
     * @param int    $percentage Progress value to display
2429
     * @param string $text_add   Text to display near the progress value
2430
     *
2431
     * @return string HTML string containing the progress bar
2432
     */
2433
    public static function get_progress_bar($percentage = -1, $text_add = '')
2434
    {
2435
        $text = $percentage.$text_add;
2436
        $output = '<div class="progress">
2437
            <div id="progress_bar_value"
2438
                class="progress-bar progress-bar-success" role="progressbar"
2439
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2440
            '.$text.'
2441
            </div>
2442
        </div>';
2443
2444
        return $output;
2445
    }
2446
2447
    /**
2448
     * @param string $mode can be '%' or 'abs'
2449
     *                     otherwise this value will be used $this->progress_bar_mode
2450
     *
2451
     * @return string
2452
     */
2453
    public function getProgressBar($mode = null)
2454
    {
2455
        [$percentage, $text_add] = $this->get_progress_bar_text($mode);
2456
2457
        return self::get_progress_bar($percentage, $text_add);
2458
    }
2459
2460
    /**
2461
     * Gets the progress bar info to display inside the progress bar.
2462
     * Also used by scorm_api.php.
2463
     *
2464
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2465
     *                     we display a number of completed elements per total elements
2466
     * @param int    $add  Additional steps to fake as completed
2467
     *
2468
     * @return array Percentage or number and symbol (% or /xx)
2469
     */
2470
    public function get_progress_bar_text($mode = '', $add = 0)
2471
    {
2472
        if (empty($mode)) {
2473
            $mode = $this->progress_bar_mode;
2474
        }
2475
        $text = '';
2476
        $percentage = 0;
2477
        // If the option to use the score as progress is set for this learning
2478
        // path, then the rules are completely different: we assume only one
2479
        // item exists and the progress of the LP depends on the score
2480
        $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
2481
        if (true === $scoreAsProgressSetting) {
2482
            $scoreAsProgress = $this->getUseScoreAsProgress();
2483
            if ($scoreAsProgress) {
2484
                // Get single item's score
2485
                $itemId = $this->get_current_item_id();
2486
                $item = $this->getItem($itemId);
2487
                $score = $item->get_score();
2488
                $maxScore = $item->get_max();
2489
                if ($mode = '%') {
2490
                    if (!empty($maxScore)) {
2491
                        $percentage = ((float) $score / (float) $maxScore) * 100;
2492
                    }
2493
                    $percentage = number_format($percentage, 0);
2494
                    $text = '%';
2495
                } else {
2496
                    $percentage = $score;
2497
                    $text = '/'.$maxScore;
2498
                }
2499
2500
                return [$percentage, $text];
2501
            }
2502
        }
2503
        // otherwise just continue the normal processing of progress
2504
        $total_items = $this->getTotalItemsCountWithoutDirs();
2505
        $completeItems = $this->get_complete_items_count();
2506
        if (0 != $add) {
2507
            $completeItems += $add;
2508
        }
2509
        if ($completeItems > $total_items) {
2510
            $completeItems = $total_items;
2511
        }
2512
        if ('%' == $mode) {
2513
            if ($total_items > 0) {
2514
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2515
            }
2516
            $percentage = number_format($percentage, 0);
2517
            $text = '%';
2518
        } elseif ('abs' === $mode) {
2519
            $percentage = $completeItems;
2520
            $text = '/'.$total_items;
2521
        }
2522
2523
        return [
2524
            $percentage,
2525
            $text,
2526
        ];
2527
    }
2528
2529
    /**
2530
     * Gets the progress bar mode.
2531
     *
2532
     * @return string The progress bar mode attribute
2533
     */
2534
    public function get_progress_bar_mode()
2535
    {
2536
        if (!empty($this->progress_bar_mode)) {
2537
            return $this->progress_bar_mode;
2538
        }
2539
2540
        return '%';
2541
    }
2542
2543
    /**
2544
     * Gets the learnpath theme (remote or local).
2545
     *
2546
     * @return string Learnpath theme
2547
     */
2548
    public function get_theme()
2549
    {
2550
        if (!empty($this->theme)) {
2551
            return $this->theme;
2552
        }
2553
2554
        return '';
2555
    }
2556
2557
    /**
2558
     * Gets the learnpath session id.
2559
     *
2560
     * @return int
2561
     */
2562
    public function get_lp_session_id()
2563
    {
2564
        if (!empty($this->lp_session_id)) {
2565
            return (int) $this->lp_session_id;
2566
        }
2567
2568
        return 0;
2569
    }
2570
2571
    /**
2572
     * Gets the learnpath author.
2573
     *
2574
     * @return string LP's author
2575
     */
2576
    public function get_author()
2577
    {
2578
        if (!empty($this->author)) {
2579
            return $this->author;
2580
        }
2581
2582
        return '';
2583
    }
2584
2585
    /**
2586
     * Gets hide table of contents.
2587
     *
2588
     * @return int
2589
     */
2590
    public function getHideTableOfContents()
2591
    {
2592
        return (int) $this->hide_toc_frame;
2593
    }
2594
2595
    /**
2596
     * Generate a new prerequisites string for a given item. If this item was a sco and
2597
     * its prerequisites were strings (instead of IDs), then transform those strings into
2598
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2599
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2600
     * same rule as the scormExport() method.
2601
     *
2602
     * @param int $item_id Item ID
2603
     *
2604
     * @return string Prerequisites string ready for the export as SCORM
2605
     */
2606
    public function get_scorm_prereq_string($item_id)
2607
    {
2608
        if ($this->debug > 0) {
2609
            error_log('In learnpath::get_scorm_prereq_string()');
2610
        }
2611
        if (!is_object($this->items[$item_id])) {
2612
            return false;
2613
        }
2614
        /** @var learnpathItem $oItem */
2615
        $oItem = $this->items[$item_id];
2616
        $prereq = $oItem->get_prereq_string();
2617
2618
        if (empty($prereq)) {
2619
            return '';
2620
        }
2621
        if (preg_match('/^\d+$/', $prereq) &&
2622
            isset($this->items[$prereq]) &&
2623
            is_object($this->items[$prereq])
2624
        ) {
2625
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2626
            // then simply return it (with the ITEM_ prefix).
2627
            //return 'ITEM_' . $prereq;
2628
            return $this->items[$prereq]->ref;
2629
        } else {
2630
            if (isset($this->refs_list[$prereq])) {
2631
                // It's a simple string item from which the ID can be found in the refs list,
2632
                // so we can transform it directly to an ID for export.
2633
                return $this->items[$this->refs_list[$prereq]]->ref;
2634
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2635
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2636
            } else {
2637
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2638
                // and replace them, one by one, by the internal IDs (chamilo db)
2639
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2640
                // by a space as well.
2641
                $find = [
2642
                    '&',
2643
                    '|',
2644
                    '~',
2645
                    '=',
2646
                    '<>',
2647
                    '{',
2648
                    '}',
2649
                    '*',
2650
                    '(',
2651
                    ')',
2652
                ];
2653
                $replace = [
2654
                    ' ',
2655
                    ' ',
2656
                    ' ',
2657
                    ' ',
2658
                    ' ',
2659
                    ' ',
2660
                    ' ',
2661
                    ' ',
2662
                    ' ',
2663
                    ' ',
2664
                ];
2665
                $prereq_mod = str_replace($find, $replace, $prereq);
2666
                $ids = explode(' ', $prereq_mod);
2667
                foreach ($ids as $id) {
2668
                    $id = trim($id);
2669
                    if (isset($this->refs_list[$id])) {
2670
                        $prereq = preg_replace(
2671
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2672
                            'ITEM_'.$this->refs_list[$id],
2673
                            $prereq
2674
                        );
2675
                    }
2676
                }
2677
2678
                return $prereq;
2679
            }
2680
        }
2681
    }
2682
2683
    /**
2684
     * Returns the XML DOM document's node.
2685
     *
2686
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2687
     * @param string   $id       The identifier to look for
2688
     *
2689
     * @return mixed The reference to the element found with that identifier. False if not found
2690
     */
2691
    public function get_scorm_xml_node(&$children, $id)
2692
    {
2693
        for ($i = 0; $i < $children->length; $i++) {
2694
            $item_temp = $children->item($i);
2695
            if ('item' == $item_temp->nodeName) {
2696
                if ($item_temp->getAttribute('identifier') == $id) {
2697
                    return $item_temp;
2698
                }
2699
            }
2700
            $subchildren = $item_temp->childNodes;
2701
            if ($subchildren && $subchildren->length > 0) {
2702
                $val = $this->get_scorm_xml_node($subchildren, $id);
2703
                if (is_object($val)) {
2704
                    return $val;
2705
                }
2706
            }
2707
        }
2708
2709
        return false;
2710
    }
2711
2712
    /**
2713
     * Gets the status list for all LP's items.
2714
     *
2715
     * @return array Array of [index] => [item ID => current status]
2716
     */
2717
    public function get_items_status_list()
2718
    {
2719
        $list = [];
2720
        foreach ($this->ordered_items as $item_id) {
2721
            $list[] = [
2722
                $item_id => $this->items[$item_id]->get_status(),
2723
            ];
2724
        }
2725
2726
        return $list;
2727
    }
2728
2729
    /**
2730
     * Return the number of interactions for the given learnpath Item View ID.
2731
     * This method can be used as static.
2732
     *
2733
     * @param int $lp_iv_id  Item View ID
2734
     * @param int $course_id course id
2735
     *
2736
     * @return int
2737
     */
2738
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2739
    {
2740
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2741
        $lp_iv_id = (int) $lp_iv_id;
2742
        $course_id = (int) $course_id;
2743
2744
        $sql = "SELECT count(*) FROM $table
2745
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2746
        $res = Database::query($sql);
2747
        $num = 0;
2748
        if (Database::num_rows($res)) {
2749
            $row = Database::fetch_array($res);
2750
            $num = $row[0];
2751
        }
2752
2753
        return $num;
2754
    }
2755
2756
    /**
2757
     * Return the interactions as an array for the given lp_iv_id.
2758
     * This method can be used as static.
2759
     *
2760
     * @param int $lp_iv_id Learnpath Item View ID
2761
     *
2762
     * @return array
2763
     *
2764
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2765
     */
2766
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2767
    {
2768
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2769
        $list = [];
2770
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2771
        $lp_iv_id = (int) $lp_iv_id;
2772
2773
        if (empty($lp_iv_id) || empty($course_id)) {
2774
            return [];
2775
        }
2776
2777
        $sql = "SELECT * FROM $table
2778
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2779
                ORDER BY order_id ASC";
2780
        $res = Database::query($sql);
2781
        $num = Database::num_rows($res);
2782
        if ($num > 0) {
2783
            $list[] = [
2784
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2785
                'id' => api_htmlentities(get_lang('Interaction ID'), ENT_QUOTES),
2786
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2787
                'time' => api_htmlentities(get_lang('Time (finished at...)'), ENT_QUOTES),
2788
                'correct_responses' => api_htmlentities(get_lang('Correct answers'), ENT_QUOTES),
2789
                'student_response' => api_htmlentities(get_lang('Learner answers'), ENT_QUOTES),
2790
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2791
                'latency' => api_htmlentities(get_lang('Time spent'), ENT_QUOTES),
2792
                'student_response_formatted' => '',
2793
            ];
2794
            while ($row = Database::fetch_array($res)) {
2795
                $studentResponseFormatted = urldecode($row['student_response']);
2796
                $content_student_response = explode('__|', $studentResponseFormatted);
2797
                if (count($content_student_response) > 0) {
2798
                    if (count($content_student_response) >= 3) {
2799
                        // Pop the element off the end of array.
2800
                        array_pop($content_student_response);
2801
                    }
2802
                    $studentResponseFormatted = implode(',', $content_student_response);
2803
                }
2804
2805
                $list[] = [
2806
                    'order_id' => $row['order_id'] + 1,
2807
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2808
                    'type' => $row['interaction_type'],
2809
                    'time' => $row['completion_time'],
2810
                    'correct_responses' => '', // Hide correct responses from students.
2811
                    'student_response' => $row['student_response'],
2812
                    'result' => $row['result'],
2813
                    'latency' => $row['latency'],
2814
                    'student_response_formatted' => $studentResponseFormatted,
2815
                ];
2816
            }
2817
        }
2818
2819
        return $list;
2820
    }
2821
2822
    /**
2823
     * Return the number of objectives for the given learnpath Item View ID.
2824
     * This method can be used as static.
2825
     *
2826
     * @param int $lp_iv_id  Item View ID
2827
     * @param int $course_id Course ID
2828
     *
2829
     * @return int Number of objectives
2830
     */
2831
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2832
    {
2833
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2834
        $course_id = (int) $course_id;
2835
        $lp_iv_id = (int) $lp_iv_id;
2836
        $sql = "SELECT count(*) FROM $table
2837
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2838
        //@todo seems that this always returns 0
2839
        $res = Database::query($sql);
2840
        $num = 0;
2841
        if (Database::num_rows($res)) {
2842
            $row = Database::fetch_array($res);
2843
            $num = $row[0];
2844
        }
2845
2846
        return $num;
2847
    }
2848
2849
    /**
2850
     * Return the objectives as an array for the given lp_iv_id.
2851
     * This method can be used as static.
2852
     *
2853
     * @param int $lpItemViewId Learnpath Item View ID
2854
     * @param int $course_id
2855
     *
2856
     * @return array
2857
     *
2858
     * @todo    Translate labels
2859
     */
2860
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
2861
    {
2862
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2863
        $lpItemViewId = (int) $lpItemViewId;
2864
2865
        if (empty($course_id) || empty($lpItemViewId)) {
2866
            return [];
2867
        }
2868
2869
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2870
        $sql = "SELECT * FROM $table
2871
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
2872
                ORDER BY order_id ASC";
2873
        $res = Database::query($sql);
2874
        $num = Database::num_rows($res);
2875
        $list = [];
2876
        if ($num > 0) {
2877
            $list[] = [
2878
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2879
                'objective_id' => api_htmlentities(get_lang('Objective ID'), ENT_QUOTES),
2880
                'score_raw' => api_htmlentities(get_lang('Objective raw score'), ENT_QUOTES),
2881
                'score_max' => api_htmlentities(get_lang('Objective max score'), ENT_QUOTES),
2882
                'score_min' => api_htmlentities(get_lang('Objective min score'), ENT_QUOTES),
2883
                'status' => api_htmlentities(get_lang('Objective status'), ENT_QUOTES),
2884
            ];
2885
            while ($row = Database::fetch_array($res)) {
2886
                $list[] = [
2887
                    'order_id' => $row['order_id'] + 1,
2888
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
2889
                    'score_raw' => $row['score_raw'],
2890
                    'score_max' => $row['score_max'],
2891
                    'score_min' => $row['score_min'],
2892
                    'status' => $row['status'],
2893
                ];
2894
            }
2895
        }
2896
2897
        return $list;
2898
    }
2899
2900
    /**
2901
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
2902
     * used by get_html_toc() to be ready to display.
2903
     *
2904
     * @return array TOC as a table with 4 elements per row: title, link, status and level
2905
     */
2906
    public function get_toc()
2907
    {
2908
        $toc = [];
2909
        foreach ($this->ordered_items as $item_id) {
2910
            // TODO: Change this link generation and use new function instead.
2911
            $toc[] = [
2912
                'id' => $item_id,
2913
                'title' => $this->items[$item_id]->get_title(),
2914
                'status' => $this->items[$item_id]->get_status(),
2915
                'level' => $this->items[$item_id]->get_level(),
2916
                'type' => $this->items[$item_id]->get_type(),
2917
                'description' => $this->items[$item_id]->get_description(),
2918
                'path' => $this->items[$item_id]->get_path(),
2919
                'parent' => $this->items[$item_id]->get_parent(),
2920
            ];
2921
        }
2922
2923
        return $toc;
2924
    }
2925
2926
    /**
2927
     * Returns the CSS class name associated with a given item status.
2928
     *
2929
     * @param $status string an item status
2930
     *
2931
     * @return string CSS class name
2932
     */
2933
    public static function getStatusCSSClassName($status)
2934
    {
2935
        if (array_key_exists($status, self::STATUS_CSS_CLASS_NAME)) {
2936
            return self::STATUS_CSS_CLASS_NAME[$status];
2937
        }
2938
2939
        return '';
2940
    }
2941
2942
    /**
2943
     * Generate the tree of contents for this learnpath as an associative array tree
2944
     * with keys id, title, status, type, description, path, parent_id, children
2945
     * (title and descriptions as secured)
2946
     * and clues for CSS class composition:
2947
     *  - booleans is_current, is_parent_of_current, is_chapter
2948
     *  - string status_css_class_name.
2949
     *
2950
     * @param $parentId int restrict returned list to children of this parent
2951
     *
2952
     * @return array TOC as a table
2953
     */
2954
    public function getTOCTree($parentId = 0)
2955
    {
2956
        $toc = [];
2957
        $currentItemId = $this->get_current_item_id();
2958
2959
        foreach ($this->ordered_items as $itemId) {
2960
            $item = $this->items[$itemId];
2961
            if ($item->get_parent() == $parentId) {
2962
                $title = $item->get_title();
2963
                if (empty($title)) {
2964
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $itemId);
2965
                }
2966
2967
                $itemData = [
2968
                    'id' => $itemId,
2969
                    'title' => Security::remove_XSS($title),
2970
                    'status' => $item->get_status(),
2971
                    'level' => $item->get_level(), // FIXME should not be needed
2972
                    'type' => $item->get_type(),
2973
                    'description' => Security::remove_XSS($item->get_description()),
2974
                    'path' => $item->get_path(),
2975
                    'parent_id' => $item->get_parent(),
2976
                    'children' => $this->getTOCTree($itemId),
2977
                    'is_current' => ($itemId == $currentItemId),
2978
                    'is_parent_of_current' => false,
2979
                    'is_chapter' => in_array($item->get_type(), self::getChapterTypes()),
2980
                    'status_css_class_name' => $this->getStatusCSSClassName($item->get_status()),
2981
                    'current_id' => $currentItemId, // FIXME should not be needed, not a property of item
2982
                ];
2983
2984
                if (!empty($itemData['children'])) {
2985
                    foreach ($itemData['children'] as $child) {
2986
                        if ($child['is_current'] || $child['is_parent_of_current']) {
2987
                            $itemData['is_parent_of_current'] = true;
2988
                            break;
2989
                        }
2990
                    }
2991
                }
2992
2993
                $toc[] = $itemData;
2994
            }
2995
        }
2996
2997
        return $toc;
2998
    }
2999
3000
    /**
3001
     * Generate and return the table of contents for this learnpath. The JS
3002
     * table returned is used inside of scorm_api.php.
3003
     *
3004
     * @param string $varname
3005
     *
3006
     * @return string A JS array variable construction
3007
     */
3008
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
3009
    {
3010
        $toc = $varname.' = new Array();';
3011
        foreach ($this->ordered_items as $item_id) {
3012
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
3013
        }
3014
3015
        return $toc;
3016
    }
3017
3018
    /**
3019
     * Gets the learning path type.
3020
     *
3021
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
3022
     *
3023
     * @return mixed Type ID or name, depending on the parameter
3024
     */
3025
    public function get_type($get_name = false)
3026
    {
3027
        $res = false;
3028
        if (!empty($this->type) && (!$get_name)) {
3029
            $res = $this->type;
3030
        }
3031
3032
        return $res;
3033
    }
3034
3035
    /**
3036
     * Gets the learning path type as static method.
3037
     *
3038
     * @param int $lp_id
3039
     *
3040
     * @return mixed Type ID or name, depending on the parameter
3041
     */
3042
    public static function get_type_static($lp_id = 0)
3043
    {
3044
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3045
        $lp_id = (int) $lp_id;
3046
        $sql = "SELECT lp_type FROM $tbl_lp
3047
                WHERE iid = $lp_id";
3048
        $res = Database::query($sql);
3049
        if (false === $res) {
3050
            return null;
3051
        }
3052
        if (Database::num_rows($res) <= 0) {
3053
            return null;
3054
        }
3055
        $row = Database::fetch_array($res);
3056
3057
        return $row['lp_type'];
3058
    }
3059
3060
    /**
3061
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3062
     * This method can be used as abstract and is recursive.
3063
     *
3064
     * @param int $lp        Learnpath ID
3065
     * @param int $parent    Parent ID of the items to look for
3066
     * @param int $course_id
3067
     *
3068
     * @return array Ordered list of item IDs (empty array on error)
3069
     */
3070
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3071
    {
3072
        if (empty($course_id)) {
3073
            $course_id = api_get_course_int_id();
3074
        } else {
3075
            $course_id = (int) $course_id;
3076
        }
3077
        $list = [];
3078
3079
        if (empty($lp)) {
3080
            return $list;
3081
        }
3082
3083
        $lp = (int) $lp;
3084
        $parent = (int) $parent;
3085
3086
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3087
        $sql = "SELECT iid FROM $tbl_lp_item
3088
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3089
                ORDER BY display_order";
3090
3091
        $res = Database::query($sql);
3092
        while ($row = Database::fetch_array($res)) {
3093
            $sublist = self::get_flat_ordered_items_list(
3094
                $lp,
3095
                $row['iid'],
3096
                $course_id
3097
            );
3098
            $list[] = $row['iid'];
3099
            foreach ($sublist as $item) {
3100
                $list[] = $item;
3101
            }
3102
        }
3103
3104
        return $list;
3105
    }
3106
3107
    /**
3108
     * @return array
3109
     */
3110
    public static function getChapterTypes()
3111
    {
3112
        return [
3113
            'dir',
3114
        ];
3115
    }
3116
3117
    /**
3118
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3119
     *
3120
     * @param $tree
3121
     *
3122
     * @return array HTML TOC ready to display
3123
     */
3124
    public function getParentToc($tree)
3125
    {
3126
        if (empty($tree)) {
3127
            $tree = $this->get_toc();
3128
        }
3129
        $dirTypes = self::getChapterTypes();
3130
        $myCurrentId = $this->get_current_item_id();
3131
        $listParent = [];
3132
        $listChildren = [];
3133
        $listNotParent = [];
3134
        $list = [];
3135
        foreach ($tree as $subtree) {
3136
            if (in_array($subtree['type'], $dirTypes)) {
3137
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3138
                $subtree['children'] = $listChildren;
3139
                if (!empty($subtree['children'])) {
3140
                    foreach ($subtree['children'] as $subItem) {
3141
                        if ($subItem['id'] == $this->current) {
3142
                            $subtree['parent_current'] = 'in';
3143
                            $subtree['current'] = 'on';
3144
                        }
3145
                    }
3146
                }
3147
                $listParent[] = $subtree;
3148
            }
3149
            if (!in_array($subtree['type'], $dirTypes) && null == $subtree['parent']) {
3150
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3151
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3152
                }
3153
3154
                $title = Security::remove_XSS($subtree['title']);
3155
                unset($subtree['title']);
3156
3157
                if (empty($title)) {
3158
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3159
                }
3160
                $classStyle = null;
3161
                if ($subtree['id'] == $this->current) {
3162
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3163
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3164
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3165
                }
3166
                $subtree['title'] = $title;
3167
                $subtree['class'] = $classStyle.' '.$cssStatus;
3168
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3169
                $subtree['current_id'] = $myCurrentId;
3170
                $listNotParent[] = $subtree;
3171
            }
3172
        }
3173
3174
        $list['are_parents'] = $listParent;
3175
        $list['not_parents'] = $listNotParent;
3176
3177
        return $list;
3178
    }
3179
3180
    /**
3181
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3182
     *
3183
     * @param array $tree
3184
     * @param int   $id
3185
     * @param bool  $parent
3186
     *
3187
     * @return array HTML TOC ready to display
3188
     */
3189
    public function getChildrenToc($tree, $id, $parent = true)
3190
    {
3191
        if (empty($tree)) {
3192
            $tree = $this->get_toc();
3193
        }
3194
3195
        $dirTypes = self::getChapterTypes();
3196
        $currentItemId = $this->get_current_item_id();
3197
        $list = [];
3198
3199
        foreach ($tree as $subtree) {
3200
            $subtree['tree'] = null;
3201
3202
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3203
                if ($subtree['id'] == $this->current) {
3204
                    $subtree['current'] = 'active';
3205
                } else {
3206
                    $subtree['current'] = null;
3207
                }
3208
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3209
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3210
                }
3211
3212
                $title = Security::remove_XSS($subtree['title']);
3213
                unset($subtree['title']);
3214
                if (empty($title)) {
3215
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3216
                }
3217
3218
                $classStyle = null;
3219
                if ($subtree['id'] == $this->current) {
3220
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3221
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3222
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3223
                }
3224
3225
                if (in_array($subtree['type'], $dirTypes)) {
3226
                    $subtree['title'] = stripslashes($title);
3227
                } else {
3228
                    $subtree['title'] = $title;
3229
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3230
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3231
                    $subtree['current_id'] = $currentItemId;
3232
                }
3233
                $list[] = $subtree;
3234
            }
3235
        }
3236
3237
        return $list;
3238
    }
3239
3240
    /**
3241
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3242
     *
3243
     * @param array $toc_list
3244
     *
3245
     * @return array HTML TOC ready to display
3246
     */
3247
    public function getListArrayToc($toc_list = [])
3248
    {
3249
        if (empty($toc_list)) {
3250
            $toc_list = $this->get_toc();
3251
        }
3252
        // Temporary variables.
3253
        $currentItemId = $this->get_current_item_id();
3254
        $list = [];
3255
        $arrayList = [];
3256
3257
        foreach ($toc_list as $item) {
3258
            $list['id'] = $item['id'];
3259
            $list['status'] = $item['status'];
3260
            $cssStatus = null;
3261
3262
            if (array_key_exists($item['status'], self::STATUS_CSS_CLASS_NAME)) {
3263
                $cssStatus = self::STATUS_CSS_CLASS_NAME[$item['status']];
3264
            }
3265
3266
            $classStyle = ' ';
3267
            $dirTypes = self::getChapterTypes();
3268
3269
            if (in_array($item['type'], $dirTypes)) {
3270
                $classStyle = 'scorm_item_section ';
3271
            }
3272
            if ($item['id'] == $this->current) {
3273
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3274
            } elseif (!in_array($item['type'], $dirTypes)) {
3275
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3276
            }
3277
            $title = $item['title'];
3278
            if (empty($title)) {
3279
                $title = self::rl_get_resource_name(
3280
                    api_get_course_id(),
3281
                    $this->get_id(),
3282
                    $item['id']
3283
                );
3284
            }
3285
            $title = Security::remove_XSS($item['title']);
3286
3287
            if (empty($item['description'])) {
3288
                $list['description'] = $title;
3289
            } else {
3290
                $list['description'] = $item['description'];
3291
            }
3292
3293
            $list['class'] = $classStyle.' '.$cssStatus;
3294
            $list['level'] = $item['level'];
3295
            $list['type'] = $item['type'];
3296
3297
            if (in_array($item['type'], $dirTypes)) {
3298
                $list['css_level'] = 'level_'.$item['level'];
3299
            } else {
3300
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3301
            }
3302
3303
            if (in_array($item['type'], $dirTypes)) {
3304
                $list['title'] = stripslashes($title);
3305
            } else {
3306
                $list['title'] = stripslashes($title);
3307
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3308
                $list['current_id'] = $currentItemId;
3309
            }
3310
            $arrayList[] = $list;
3311
        }
3312
3313
        return $arrayList;
3314
    }
3315
3316
    /**
3317
     * Returns an HTML-formatted string ready to display with teacher buttons
3318
     * in LP view menu.
3319
     *
3320
     * @return string HTML TOC ready to display
3321
     */
3322
    public function get_teacher_toc_buttons()
3323
    {
3324
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3325
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3326
        $html = '';
3327
        if ($isAllow && false == $hideIcons) {
3328
            if ($this->get_lp_session_id() == api_get_session_id()) {
3329
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3330
                $html .= '<div class="btn-group">';
3331
                $html .= "<a class='btn btn-sm btn-default' href='lp_controller.php?".api_get_cidreq()."&action=build&lp_id=".$this->lp_id."&isStudentView=false' target='_parent'>".
3332
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3333
                $html .= "<a class='btn btn-sm btn-default' href='lp_controller.php?".api_get_cidreq()."&action=add_item&type=step&lp_id=".$this->lp_id."&isStudentView=false' target='_parent'>".
3334
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3335
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3336
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3337
                $html .= '</div>';
3338
                $html .= '</div>';
3339
            }
3340
        }
3341
3342
        return $html;
3343
    }
3344
3345
    /**
3346
     * Gets the learnpath maker name - generally the editor's name.
3347
     *
3348
     * @return string Learnpath maker name
3349
     */
3350
    public function get_maker()
3351
    {
3352
        if (!empty($this->maker)) {
3353
            return $this->maker;
3354
        }
3355
3356
        return '';
3357
    }
3358
3359
    /**
3360
     * Gets the learnpath name/title.
3361
     *
3362
     * @return string Learnpath name/title
3363
     */
3364
    public function get_name()
3365
    {
3366
        if (!empty($this->name)) {
3367
            return $this->name;
3368
        }
3369
3370
        return 'N/A';
3371
    }
3372
3373
    /**
3374
     * @return string
3375
     */
3376
    public function getNameNoTags()
3377
    {
3378
        return strip_tags($this->get_name());
3379
    }
3380
3381
    /**
3382
     * Gets a link to the resource from the present location, depending on item ID.
3383
     *
3384
     * @param string $type         Type of link expected
3385
     * @param int    $item_id      Learnpath item ID
3386
     * @param bool   $provided_toc
3387
     *
3388
     * @return string $provided_toc Link to the lp_item resource
3389
     */
3390
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
3391
    {
3392
        $course_id = $this->get_course_int_id();
3393
        $item_id = (int) $item_id;
3394
3395
        if (empty($item_id)) {
3396
            $item_id = $this->get_current_item_id();
3397
3398
            if (empty($item_id)) {
3399
                //still empty, this means there was no item_id given and we are not in an object context or
3400
                //the object property is empty, return empty link
3401
                $this->first();
3402
3403
                return '';
3404
            }
3405
        }
3406
3407
        $file = '';
3408
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3409
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3410
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3411
3412
        $sql = "SELECT
3413
                    l.lp_type as ltype,
3414
                    l.path as lpath,
3415
                    li.item_type as litype,
3416
                    li.path as lipath,
3417
                    li.parameters as liparams
3418
        		FROM $lp_table l
3419
                INNER JOIN $lp_item_table li
3420
                ON (li.lp_id = l.iid)
3421
        		WHERE
3422
        		    li.iid = $item_id
3423
        		";
3424
        $res = Database::query($sql);
3425
        if (Database::num_rows($res) > 0) {
3426
            $row = Database::fetch_array($res);
3427
            $lp_type = $row['ltype'];
3428
            $lp_path = $row['lpath'];
3429
            $lp_item_type = $row['litype'];
3430
            $lp_item_path = $row['lipath'];
3431
            $lp_item_params = $row['liparams'];
3432
            if (empty($lp_item_params) && false !== strpos($lp_item_path, '?')) {
3433
                [$lp_item_path, $lp_item_params] = explode('?', $lp_item_path);
3434
            }
3435
            //$sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3436
            if ('http' === $type) {
3437
                //web path
3438
                //$course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3439
            } else {
3440
                //$course_path = $sys_course_path; //system path
3441
            }
3442
3443
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3444
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3445
            if (in_array(
3446
                $lp_item_type,
3447
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3448
            )
3449
            ) {
3450
                $lp_type = CLp::LP_TYPE;
3451
            }
3452
3453
            // Now go through the specific cases to get the end of the path
3454
            // @todo Use constants instead of int values.
3455
            switch ($lp_type) {
3456
                case CLp::LP_TYPE:
3457
                    $file = self::rl_get_resource_link_for_learnpath(
3458
                        $course_id,
3459
                        $this->get_id(),
3460
                        $item_id,
3461
                        $this->get_view_id()
3462
                    );
3463
                    switch ($lp_item_type) {
3464
                        case 'document':
3465
                            // Shows a button to download the file instead of just downloading the file directly.
3466
                            $documentPathInfo = pathinfo($file);
3467
                            if (isset($documentPathInfo['extension'])) {
3468
                                $parsed = parse_url($documentPathInfo['extension']);
3469
                                if (isset($parsed['path'])) {
3470
                                    $extension = $parsed['path'];
3471
                                    $extensionsToDownload = [
3472
                                        'zip',
3473
                                        'ppt',
3474
                                        'pptx',
3475
                                        'ods',
3476
                                        'xlsx',
3477
                                        'xls',
3478
                                        'csv',
3479
                                        'doc',
3480
                                        'docx',
3481
                                        'dot',
3482
                                    ];
3483
3484
                                    if (in_array($extension, $extensionsToDownload)) {
3485
                                        $file = api_get_path(WEB_CODE_PATH).
3486
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
3487
                                    }
3488
                                }
3489
                            }
3490
                            break;
3491
                        case 'dir':
3492
                            $file = 'lp_content.php?type=dir';
3493
                            break;
3494
                        case 'link':
3495
                            if (Link::is_youtube_link($file)) {
3496
                                $src = Link::get_youtube_video_id($file);
3497
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3498
                            } elseif (Link::isVimeoLink($file)) {
3499
                                $src = Link::getVimeoLinkId($file);
3500
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3501
                            } else {
3502
                                // If the current site is HTTPS and the link is
3503
                                // HTTP, browsers will refuse opening the link
3504
                                $urlId = api_get_current_access_url_id();
3505
                                $url = api_get_access_url($urlId, false);
3506
                                $protocol = substr($url['url'], 0, 5);
3507
                                if ('https' === $protocol) {
3508
                                    $linkProtocol = substr($file, 0, 5);
3509
                                    if ('http:' === $linkProtocol) {
3510
                                        //this is the special intervention case
3511
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3512
                                    }
3513
                                }
3514
                            }
3515
                            break;
3516
                        case 'quiz':
3517
                            // Check how much attempts of a exercise exits in lp
3518
                            $lp_item_id = $this->get_current_item_id();
3519
                            $lp_view_id = $this->get_view_id();
3520
3521
                            $prevent_reinit = null;
3522
                            if (isset($this->items[$this->current])) {
3523
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3524
                            }
3525
3526
                            if (empty($provided_toc)) {
3527
                                $list = $this->get_toc();
3528
                            } else {
3529
                                $list = $provided_toc;
3530
                            }
3531
3532
                            $type_quiz = false;
3533
                            foreach ($list as $toc) {
3534
                                if ($toc['id'] == $lp_item_id && 'quiz' == $toc['type']) {
3535
                                    $type_quiz = true;
3536
                                }
3537
                            }
3538
3539
                            if ($type_quiz) {
3540
                                $lp_item_id = (int) $lp_item_id;
3541
                                $lp_view_id = (int) $lp_view_id;
3542
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3543
                                        WHERE
3544
                                            c_id = $course_id AND
3545
                                            lp_item_id='".$lp_item_id."' AND
3546
                                            lp_view_id ='".$lp_view_id."' AND
3547
                                            status='completed'";
3548
                                $result = Database::query($sql);
3549
                                $row_count = Database:: fetch_row($result);
3550
                                $count_item_view = (int) $row_count[0];
3551
                                $not_multiple_attempt = 0;
3552
                                if (1 === $prevent_reinit && $count_item_view > 0) {
3553
                                    $not_multiple_attempt = 1;
3554
                                }
3555
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3556
                            }
3557
                            break;
3558
                    }
3559
3560
                    $tmp_array = explode('/', $file);
3561
                    $document_name = $tmp_array[count($tmp_array) - 1];
3562
                    if (strpos($document_name, '_DELETED_')) {
3563
                        $file = 'blank.php?error=document_deleted';
3564
                    }
3565
                    break;
3566
                case CLp::SCORM_TYPE:
3567
                    if ('dir' !== $lp_item_type) {
3568
                        // Quite complex here:
3569
                        // We want to make sure 'http://' (and similar) links can
3570
                        // be loaded as is (withouth the Chamilo path in front) but
3571
                        // some contents use this form: resource.htm?resource=http://blablabla
3572
                        // which means we have to find a protocol at the path's start, otherwise
3573
                        // it should not be considered as an external URL.
3574
                        // if ($this->prerequisites_match($item_id)) {
3575
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
3576
                            if ($this->debug > 2) {
3577
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3578
                            }
3579
                            // Distant url, return as is.
3580
                            $file = $lp_item_path;
3581
                        } else {
3582
                            if ($this->debug > 2) {
3583
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path);
3584
                            }
3585
                            // Prevent getting untranslatable urls.
3586
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3587
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3588
3589
                            $asset = $this->getEntity()->getAsset();
3590
                            $folder = Container::getAssetRepository()->getFolder($asset);
3591
                            $hasFile = Container::getAssetRepository()->getFileSystem()->has($folder.$lp_item_path);
3592
                            $file = null;
3593
                            if ($hasFile) {
3594
                                $file = Container::getAssetRepository()->getAssetUrl($asset).'/'.$lp_item_path;
3595
                            }
3596
3597
                            error_log($file);
3598
3599
                            // Prepare the path.
3600
                            /*$file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3601
                            // TODO: Fix this for urls with protocol header.
3602
                            $file = str_replace('//', '/', $file);
3603
                            $file = str_replace(':/', '://', $file);
3604
                            if ('/' === substr($lp_path, -1)) {
3605
                                $lp_path = substr($lp_path, 0, -1);
3606
                            }*/
3607
                            if (!$hasFile) {
3608
                                // if file not found.
3609
                                $decoded = html_entity_decode($lp_item_path);
3610
                                [$decoded] = explode('?', $decoded);
3611
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3612
                                    $file = self::rl_get_resource_link_for_learnpath(
3613
                                        $course_id,
3614
                                        $this->get_id(),
3615
                                        $item_id,
3616
                                        $this->get_view_id()
3617
                                    );
3618
                                    if (empty($file)) {
3619
                                        $file = 'blank.php?error=document_not_found';
3620
                                    } else {
3621
                                        $tmp_array = explode('/', $file);
3622
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3623
                                        if (strpos($document_name, '_DELETED_')) {
3624
                                            $file = 'blank.php?error=document_deleted';
3625
                                        } else {
3626
                                            $file = 'blank.php?error=document_not_found';
3627
                                        }
3628
                                    }
3629
                                } else {
3630
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3631
                                }
3632
                            }
3633
                        }
3634
3635
                        // We want to use parameters if they were defined in the imsmanifest
3636
                        if (false === strpos($file, 'blank.php')) {
3637
                            $lp_item_params = ltrim($lp_item_params, '?');
3638
                            $file .= (false === strstr($file, '?') ? '?' : '').$lp_item_params;
3639
                        }
3640
                    } else {
3641
                        $file = 'lp_content.php?type=dir';
3642
                    }
3643
                    break;
3644
                case CLp::AICC_TYPE:
3645
                    // Formatting AICC HACP append URL.
3646
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3647
                    if (!empty($lp_item_params)) {
3648
                        $aicc_append .= $lp_item_params.'&';
3649
                    }
3650
                    if ('dir' !== $lp_item_type) {
3651
                        // Quite complex here:
3652
                        // We want to make sure 'http://' (and similar) links can
3653
                        // be loaded as is (withouth the Chamilo path in front) but
3654
                        // some contents use this form: resource.htm?resource=http://blablabla
3655
                        // which means we have to find a protocol at the path's start, otherwise
3656
                        // it should not be considered as an external URL.
3657
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
3658
                            if ($this->debug > 2) {
3659
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3660
                            }
3661
                            // Distant url, return as is.
3662
                            $file = $lp_item_path;
3663
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3664
                            /*
3665
                            if (stristr($file,'<servername>') !== false) {
3666
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3667
                            }
3668
                            */
3669
                            if (false !== stripos($file, '<servername>')) {
3670
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3671
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3672
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3673
                            }
3674
3675
                            $file .= $aicc_append;
3676
                        } else {
3677
                            if ($this->debug > 2) {
3678
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3679
                            }
3680
                            // Prevent getting untranslatable urls.
3681
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3682
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3683
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3684
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3685
                            // TODO: Fix this for urls with protocol header.
3686
                            $file = str_replace('//', '/', $file);
3687
                            $file = str_replace(':/', '://', $file);
3688
                            $file .= $aicc_append;
3689
                        }
3690
                    } else {
3691
                        $file = 'lp_content.php?type=dir';
3692
                    }
3693
                    break;
3694
                case 4:
3695
                default:
3696
                    break;
3697
            }
3698
            // Replace &amp; by & because &amp; will break URL with params
3699
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3700
        }
3701
        if ($this->debug > 2) {
3702
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3703
        }
3704
3705
        return $file;
3706
    }
3707
3708
    /**
3709
     * Gets the latest usable view or generate a new one.
3710
     *
3711
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3712
     * @param int $userId      The user ID, as $this->get_user_id() is not always available
3713
     *
3714
     * @return int DB lp_view id
3715
     */
3716
    public function get_view($attempt_num = 0, $userId = null)
3717
    {
3718
        $search = '';
3719
        // Use $attempt_num to enable multi-views management (disabled so far).
3720
        if (0 != $attempt_num && intval(strval($attempt_num)) == $attempt_num) {
3721
            $search = 'AND view_count = '.$attempt_num;
3722
        }
3723
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3724
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3725
3726
        $course_id = api_get_course_int_id();
3727
        $sessionId = api_get_session_id();
3728
3729
        // Check user ID.
3730
        if (empty($userId)) {
3731
            if (empty($this->get_user_id())) {
3732
                $this->error = 'User ID is empty in learnpath::get_view()';
3733
3734
                return null;
3735
            } else {
3736
                $userId = $this->get_user_id();
3737
            }
3738
        }
3739
3740
        $sql = "SELECT iid, view_count FROM $lp_view_table
3741
        		WHERE
3742
        		    c_id = $course_id AND
3743
        		    lp_id = ".$this->get_id()." AND
3744
        		    user_id = ".$userId." AND
3745
        		    session_id = $sessionId
3746
        		    $search
3747
                ORDER BY view_count DESC";
3748
        $res = Database::query($sql);
3749
        if (Database::num_rows($res) > 0) {
3750
            $row = Database::fetch_array($res);
3751
            $this->lp_view_id = $row['iid'];
3752
        } elseif (!api_is_invitee()) {
3753
            // There is no database record, create one.
3754
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3755
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3756
            Database::query($sql);
3757
            $id = Database::insert_id();
3758
            $this->lp_view_id = $id;
3759
        }
3760
3761
        return $this->lp_view_id;
3762
    }
3763
3764
    /**
3765
     * Gets the current view id.
3766
     *
3767
     * @return int View ID (from lp_view)
3768
     */
3769
    public function get_view_id()
3770
    {
3771
        if (!empty($this->lp_view_id)) {
3772
            return (int) $this->lp_view_id;
3773
        }
3774
3775
        return 0;
3776
    }
3777
3778
    /**
3779
     * Gets the update queue.
3780
     *
3781
     * @return array Array containing IDs of items to be updated by JavaScript
3782
     */
3783
    public function get_update_queue()
3784
    {
3785
        return $this->update_queue;
3786
    }
3787
3788
    /**
3789
     * Gets the user ID.
3790
     *
3791
     * @return int User ID
3792
     */
3793
    public function get_user_id()
3794
    {
3795
        if (!empty($this->user_id)) {
3796
            return (int) $this->user_id;
3797
        }
3798
3799
        return false;
3800
    }
3801
3802
    /**
3803
     * Checks if any of the items has an audio element attached.
3804
     *
3805
     * @return bool True or false
3806
     */
3807
    public function has_audio()
3808
    {
3809
        $has = false;
3810
        foreach ($this->items as $i => $item) {
3811
            if (!empty($this->items[$i]->audio)) {
3812
                $has = true;
3813
                break;
3814
            }
3815
        }
3816
3817
        return $has;
3818
    }
3819
3820
    /**
3821
     * Moves an item up and down at its level.
3822
     *
3823
     * @param int    $id        Item to move up and down
3824
     * @param string $direction Direction 'up' or 'down'
3825
     *
3826
     * @return bool|int
3827
     */
3828
    public function move_item($id, $direction)
3829
    {
3830
        $course_id = api_get_course_int_id();
3831
        if (empty($id) || empty($direction)) {
3832
            return false;
3833
        }
3834
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3835
        $sql_sel = "SELECT *
3836
                    FROM $tbl_lp_item
3837
                    WHERE
3838
                        iid = $id
3839
                    ";
3840
        $res_sel = Database::query($sql_sel);
3841
        // Check if elem exists.
3842
        if (Database::num_rows($res_sel) < 1) {
3843
            return false;
3844
        }
3845
        // Gather data.
3846
        $row = Database::fetch_array($res_sel);
3847
        $previous = $row['previous_item_id'];
3848
        $next = $row['next_item_id'];
3849
        $display = $row['display_order'];
3850
        $parent = $row['parent_item_id'];
3851
        $lp = $row['lp_id'];
3852
        // Update the item (switch with previous/next one).
3853
        switch ($direction) {
3854
            case 'up':
3855
                if ($display > 1) {
3856
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3857
                                 WHERE iid = $previous";
3858
                    $res_sel2 = Database::query($sql_sel2);
3859
                    if (Database::num_rows($res_sel2) < 1) {
3860
                        $previous_previous = 0;
3861
                    }
3862
                    // Gather data.
3863
                    $row2 = Database::fetch_array($res_sel2);
3864
                    $previous_previous = $row2['previous_item_id'];
3865
                    // Update previous_previous item (switch "next" with current).
3866
                    if (0 != $previous_previous) {
3867
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3868
                                        next_item_id = $id
3869
                                    WHERE iid = $previous_previous";
3870
                        Database::query($sql_upd2);
3871
                    }
3872
                    // Update previous item (switch with current).
3873
                    if (0 != $previous) {
3874
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3875
                                    next_item_id = $next,
3876
                                    previous_item_id = $id,
3877
                                    display_order = display_order +1
3878
                                    WHERE iid = $previous";
3879
                        Database::query($sql_upd2);
3880
                    }
3881
3882
                    // Update current item (switch with previous).
3883
                    if (0 != $id) {
3884
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3885
                                        next_item_id = $previous,
3886
                                        previous_item_id = $previous_previous,
3887
                                        display_order = display_order-1
3888
                                    WHERE c_id = ".$course_id." AND id = $id";
3889
                        Database::query($sql_upd2);
3890
                    }
3891
                    // Update next item (new previous item).
3892
                    if (!empty($next)) {
3893
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
3894
                                     WHERE iid = $next";
3895
                        Database::query($sql_upd2);
3896
                    }
3897
                    $display = $display - 1;
3898
                }
3899
                break;
3900
            case 'down':
3901
                if (0 != $next) {
3902
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3903
                                 WHERE iid = $next";
3904
                    $res_sel2 = Database::query($sql_sel2);
3905
                    if (Database::num_rows($res_sel2) < 1) {
3906
                        $next_next = 0;
3907
                    }
3908
                    // Gather data.
3909
                    $row2 = Database::fetch_array($res_sel2);
3910
                    $next_next = $row2['next_item_id'];
3911
                    // Update previous item (switch with current).
3912
                    if (0 != $previous) {
3913
                        $sql_upd2 = "UPDATE $tbl_lp_item
3914
                                     SET next_item_id = $next
3915
                                     WHERE iid = $previous";
3916
                        Database::query($sql_upd2);
3917
                    }
3918
                    // Update current item (switch with previous).
3919
                    if (0 != $id) {
3920
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3921
                                     previous_item_id = $next,
3922
                                     next_item_id = $next_next,
3923
                                     display_order = display_order + 1
3924
                                     WHERE iid = $id";
3925
                        Database::query($sql_upd2);
3926
                    }
3927
3928
                    // Update next item (new previous item).
3929
                    if (0 != $next) {
3930
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3931
                                     previous_item_id = $previous,
3932
                                     next_item_id = $id,
3933
                                     display_order = display_order-1
3934
                                     WHERE iid = $next";
3935
                        Database::query($sql_upd2);
3936
                    }
3937
3938
                    // Update next_next item (switch "previous" with current).
3939
                    if (0 != $next_next) {
3940
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3941
                                     previous_item_id = $id
3942
                                     WHERE iid = $next_next";
3943
                        Database::query($sql_upd2);
3944
                    }
3945
                    $display = $display + 1;
3946
                }
3947
                break;
3948
            default:
3949
                return false;
3950
        }
3951
3952
        return $display;
3953
    }
3954
3955
    /**
3956
     * Move a LP up (display_order).
3957
     *
3958
     * @param int $lp_id      Learnpath ID
3959
     * @param int $categoryId Category ID
3960
     *
3961
     * @return bool
3962
     */
3963
    public static function move_up($lp_id, $categoryId = 0)
3964
    {
3965
        $courseId = api_get_course_int_id();
3966
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3967
3968
        $categoryCondition = '';
3969
        if (!empty($categoryId)) {
3970
            $categoryId = (int) $categoryId;
3971
            $categoryCondition = " AND category_id = $categoryId";
3972
        }
3973
        $sql = "SELECT * FROM $lp_table
3974
                WHERE c_id = $courseId
3975
                $categoryCondition
3976
                ORDER BY display_order";
3977
        $res = Database::query($sql);
3978
        if (false === $res) {
3979
            return false;
3980
        }
3981
3982
        $lps = [];
3983
        $lp_order = [];
3984
        $num = Database::num_rows($res);
3985
        // First check the order is correct, globally (might be wrong because
3986
        // of versions < 1.8.4)
3987
        if ($num > 0) {
3988
            $i = 1;
3989
            while ($row = Database::fetch_array($res)) {
3990
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
3991
                    $sql = "UPDATE $lp_table SET display_order = $i
3992
                            WHERE iid = ".$row['iid'];
3993
                    Database::query($sql);
3994
                }
3995
                $row['display_order'] = $i;
3996
                $lps[$row['iid']] = $row;
3997
                $lp_order[$i] = $row['iid'];
3998
                $i++;
3999
            }
4000
        }
4001
        if ($num > 1) { // If there's only one element, no need to sort.
4002
            $order = $lps[$lp_id]['display_order'];
4003
            if ($order > 1) { // If it's the first element, no need to move up.
4004
                $sql = "UPDATE $lp_table SET display_order = $order
4005
                        WHERE iid = ".$lp_order[$order - 1];
4006
                Database::query($sql);
4007
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
4008
                        WHERE iid = $lp_id";
4009
                Database::query($sql);
4010
            }
4011
        }
4012
4013
        return true;
4014
    }
4015
4016
    /**
4017
     * Move a learnpath down (display_order).
4018
     *
4019
     * @param int $lp_id      Learnpath ID
4020
     * @param int $categoryId Category ID
4021
     *
4022
     * @return bool
4023
     */
4024
    public static function move_down($lp_id, $categoryId = 0)
4025
    {
4026
        $courseId = api_get_course_int_id();
4027
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4028
4029
        $categoryCondition = '';
4030
        if (!empty($categoryId)) {
4031
            $categoryId = (int) $categoryId;
4032
            $categoryCondition = " AND category_id = $categoryId";
4033
        }
4034
4035
        $sql = "SELECT * FROM $lp_table
4036
                WHERE c_id = $courseId
4037
                $categoryCondition
4038
                ORDER BY display_order";
4039
        $res = Database::query($sql);
4040
        if (false === $res) {
4041
            return false;
4042
        }
4043
        $lps = [];
4044
        $lp_order = [];
4045
        $num = Database::num_rows($res);
4046
        $max = 0;
4047
        // First check the order is correct, globally (might be wrong because
4048
        // of versions < 1.8.4).
4049
        if ($num > 0) {
4050
            $i = 1;
4051
            while ($row = Database::fetch_array($res)) {
4052
                $max = $i;
4053
                if ($row['display_order'] != $i) {
4054
                    // If we find a gap in the order, we need to fix it.
4055
                    $sql = "UPDATE $lp_table SET display_order = $i
4056
                              WHERE iid = ".$row['iid'];
4057
                    Database::query($sql);
4058
                }
4059
                $row['display_order'] = $i;
4060
                $lps[$row['iid']] = $row;
4061
                $lp_order[$i] = $row['iid'];
4062
                $i++;
4063
            }
4064
        }
4065
        if ($num > 1) { // If there's only one element, no need to sort.
4066
            $order = $lps[$lp_id]['display_order'];
4067
            if ($order < $max) { // If it's the first element, no need to move up.
4068
                $sql = "UPDATE $lp_table SET display_order = $order
4069
                        WHERE iid = ".$lp_order[$order + 1];
4070
                Database::query($sql);
4071
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4072
                        WHERE iid = $lp_id";
4073
                Database::query($sql);
4074
            }
4075
        }
4076
4077
        return true;
4078
    }
4079
4080
    /**
4081
     * Updates learnpath attributes to point to the next element
4082
     * The last part is similar to set_current_item but processing the other way around.
4083
     */
4084
    public function next()
4085
    {
4086
        if ($this->debug > 0) {
4087
            error_log('In learnpath::next()', 0);
4088
        }
4089
        $this->last = $this->get_current_item_id();
4090
        $this->items[$this->last]->save(
4091
            false,
4092
            $this->prerequisites_match($this->last)
4093
        );
4094
        $this->autocomplete_parents($this->last);
4095
        $new_index = $this->get_next_index();
4096
        if ($this->debug > 2) {
4097
            error_log('New index: '.$new_index, 0);
4098
        }
4099
        $this->index = $new_index;
4100
        if ($this->debug > 2) {
4101
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4102
        }
4103
        $this->current = $this->ordered_items[$new_index];
4104
        if ($this->debug > 2) {
4105
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4106
        }
4107
    }
4108
4109
    /**
4110
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4111
     * class, this might be redefined to allow several behaviours depending on the document type.
4112
     *
4113
     * @param int $id Resource ID
4114
     */
4115
    public function open($id)
4116
    {
4117
        // TODO:
4118
        // set the current resource attribute to this resource
4119
        // switch on element type (redefine in child class?)
4120
        // set status for this item to "opened"
4121
        // start timer
4122
        // initialise score
4123
        $this->index = 0; //or = the last item seen (see $this->last)
4124
    }
4125
4126
    /**
4127
     * Check that all prerequisites are fulfilled. Returns true and an
4128
     * empty string on success, returns false
4129
     * and the prerequisite string on error.
4130
     * This function is based on the rules for aicc_script language as
4131
     * described in the SCORM 1.2 CAM documentation page 108.
4132
     *
4133
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4134
     *
4135
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4136
     *              string otherwise
4137
     */
4138
    public function prerequisites_match($itemId = null)
4139
    {
4140
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4141
        if ($allow) {
4142
            if (api_is_allowed_to_edit() ||
4143
                api_is_platform_admin(true) ||
4144
                api_is_drh() ||
4145
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4146
            ) {
4147
                return true;
4148
            }
4149
        }
4150
4151
        $debug = $this->debug;
4152
        if ($debug > 0) {
4153
            error_log('In learnpath::prerequisites_match()');
4154
        }
4155
4156
        if (empty($itemId)) {
4157
            $itemId = $this->current;
4158
        }
4159
4160
        $currentItem = $this->getItem($itemId);
4161
4162
        if ($currentItem) {
4163
            if (2 == $this->type) {
4164
                // Getting prereq from scorm
4165
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4166
            } else {
4167
                $prereq_string = $currentItem->get_prereq_string();
4168
            }
4169
4170
            if (empty($prereq_string)) {
4171
                if ($debug > 0) {
4172
                    error_log('Found prereq_string is empty return true');
4173
                }
4174
4175
                return true;
4176
            }
4177
4178
            // Clean spaces.
4179
            $prereq_string = str_replace(' ', '', $prereq_string);
4180
            if ($debug > 0) {
4181
                error_log('Found prereq_string: '.$prereq_string, 0);
4182
            }
4183
4184
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4185
            $result = $currentItem->parse_prereq(
4186
                $prereq_string,
4187
                $this->items,
4188
                $this->refs_list,
4189
                $this->get_user_id()
4190
            );
4191
4192
            if (false === $result) {
4193
                $this->set_error_msg($currentItem->prereq_alert);
4194
            }
4195
        } else {
4196
            $result = true;
4197
            if ($debug > 1) {
4198
                error_log('$this->items['.$itemId.'] was not an object', 0);
4199
            }
4200
        }
4201
4202
        if ($debug > 1) {
4203
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4204
        }
4205
4206
        return $result;
4207
    }
4208
4209
    /**
4210
     * Updates learnpath attributes to point to the previous element
4211
     * The last part is similar to set_current_item but processing the other way around.
4212
     */
4213
    public function previous()
4214
    {
4215
        $this->last = $this->get_current_item_id();
4216
        $this->items[$this->last]->save(
4217
            false,
4218
            $this->prerequisites_match($this->last)
4219
        );
4220
        $this->autocomplete_parents($this->last);
4221
        $new_index = $this->get_previous_index();
4222
        $this->index = $new_index;
4223
        $this->current = $this->ordered_items[$new_index];
4224
    }
4225
4226
    /**
4227
     * Publishes a learnpath. This basically means show or hide the learnpath
4228
     * to normal users.
4229
     * Can be used as abstract.
4230
     *
4231
     * @param int $id         Learnpath ID
4232
     * @param int $visibility New visibility (1 = visible/published, 0= invisible/draft)
4233
     *
4234
     * @return bool
4235
     */
4236
    public static function toggleVisibility($id, $visibility = 1)
4237
    {
4238
        $repo = Container::getLpRepository();
4239
        $lp = $repo->find($id);
4240
4241
        if (!$lp) {
4242
            return false;
4243
        }
4244
4245
        $visibility = (int) $visibility;
4246
4247
        if (1 === $visibility) {
4248
            $repo->setVisibilityPublished($lp);
4249
        } else {
4250
            $repo->setVisibilityDraft($lp);
4251
        }
4252
4253
        return true;
4254
4255
        /*$action = 'visible';
4256
        if (1 != $set_visibility) {
4257
            $action = 'invisible';
4258
            self::toggle_publish($lp_id, 'i');
4259
        }
4260
4261
        return api_item_property_update(
4262
            api_get_course_info(),
4263
            TOOL_LEARNPATH,
4264
            $lp_id,
4265
            $action,
4266
            api_get_user_id()
4267
        );*/
4268
    }
4269
4270
    /**
4271
     * Publishes a learnpath category.
4272
     * This basically means show or hide the learnpath category to normal users.
4273
     *
4274
     * @param int $id
4275
     * @param int $visibility
4276
     *
4277
     * @return bool
4278
     */
4279
    public static function toggleCategoryVisibility($id, $visibility = 1)
4280
    {
4281
        $repo = Container::getLpCategoryRepository();
4282
        $resource = $repo->find($id);
4283
4284
        if (!$resource) {
4285
            return false;
4286
        }
4287
4288
        $visibility = (int) $visibility;
4289
4290
        if (1 === $visibility) {
4291
            $repo->setVisibilityPublished($resource);
4292
        } else {
4293
            $repo->setVisibilityDraft($resource);
4294
            self::toggleCategoryPublish($id, 0);
4295
        }
4296
4297
        return false;
4298
        /*
4299
        $action = 'visible';
4300
        if (1 != $visibility) {
4301
            self::toggleCategoryPublish($id, 0);
4302
            $action = 'invisible';
4303
        }
4304
4305
        return api_item_property_update(
4306
            api_get_course_info(),
4307
            TOOL_LEARNPATH_CATEGORY,
4308
            $id,
4309
            $action,
4310
            api_get_user_id()
4311
        );*/
4312
    }
4313
4314
    /**
4315
     * Publishes a learnpath. This basically means show or hide the learnpath
4316
     * on the course homepage.
4317
     *
4318
     * @param int    $id            Learnpath id
4319
     * @param string $setVisibility New visibility (v/i - visible/invisible)
4320
     *
4321
     * @return bool
4322
     */
4323
    public static function togglePublish($id, $setVisibility = 'v')
4324
    {
4325
        $addShortcut = false;
4326
        if ('v' === $setVisibility) {
4327
            $addShortcut = true;
4328
        }
4329
        $repo = Container::getLpRepository();
4330
        /** @var CLp $lp */
4331
        $lp = $repo->find($id);
4332
        if (null === $lp) {
4333
            return false;
4334
        }
4335
        $repoShortcut = Container::getShortcutRepository();
4336
        $courseEntity = api_get_course_entity();
4337
4338
        if ($addShortcut) {
4339
            $repoShortcut->addShortCut($lp, $courseEntity, $courseEntity, api_get_session_entity());
4340
        } else {
4341
            $repoShortcut->removeShortCut($lp);
4342
        }
4343
4344
        return true;
4345
4346
        /*
4347
        $course_id = api_get_course_int_id();
4348
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4349
        $lp_id = (int) $lp_id;
4350
        $sql = "SELECT * FROM $tbl_lp
4351
                WHERE iid = $lp_id";
4352
        $result = Database::query($sql);
4353
4354
        if (Database::num_rows($result)) {
4355
            $row = Database::fetch_array($result);
4356
            $name = Database::escape_string($row['name']);
4357
            if ($set_visibility == 'i') {
4358
                $v = 0;
4359
            }
4360
            if ($set_visibility == 'v') {
4361
                $v = 1;
4362
            }
4363
4364
            $session_id = api_get_session_id();
4365
            $session_condition = api_get_session_condition($session_id);
4366
4367
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4368
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4369
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4370
4371
            $sql = "SELECT * FROM $tbl_tool
4372
                    WHERE
4373
                        c_id = $course_id AND
4374
                        (link = '$link' OR link = '$oldLink') AND
4375
                        image = 'scormbuilder.gif' AND
4376
                        (
4377
                            link LIKE '$link%' OR
4378
                            link LIKE '$oldLink%'
4379
                        )
4380
                        $session_condition
4381
                    ";
4382
4383
            $result = Database::query($sql);
4384
            $num = Database::num_rows($result);
4385
            if ($set_visibility == 'i' && $num > 0) {
4386
                $sql = "DELETE FROM $tbl_tool
4387
                        WHERE
4388
                            c_id = $course_id AND
4389
                            (link = '$link' OR link = '$oldLink') AND
4390
                            image='scormbuilder.gif'
4391
                            $session_condition";
4392
                Database::query($sql);
4393
            } elseif ($set_visibility == 'v' && $num == 0) {
4394
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4395
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4396
                Database::query($sql);
4397
                $insertId = Database::insert_id();
4398
                if ($insertId) {
4399
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4400
                    Database::query($sql);
4401
                }
4402
            } elseif ($set_visibility == 'v' && $num > 0) {
4403
                $sql = "UPDATE $tbl_tool SET
4404
                            c_id = $course_id,
4405
                            name = '$name',
4406
                            link = '$link',
4407
                            image = 'scormbuilder.gif',
4408
                            visibility = '$v',
4409
                            admin = '0',
4410
                            address = 'pastillegris.gif',
4411
                            added_tool = 0,
4412
                            session_id = $session_id
4413
                        WHERE
4414
                            c_id = ".$course_id." AND
4415
                            (link = '$link' OR link = '$oldLink') AND
4416
                            image='scormbuilder.gif'
4417
                            $session_condition
4418
                        ";
4419
                Database::query($sql);
4420
            } else {
4421
                // Parameter and database incompatible, do nothing, exit.
4422
                return false;
4423
            }
4424
        } else {
4425
            return false;
4426
        }*/
4427
    }
4428
4429
    /**
4430
     * Show or hide the learnpath category on the course homepage.
4431
     *
4432
     * @param int $id
4433
     * @param int $setVisibility
4434
     *
4435
     * @return bool
4436
     */
4437
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4438
    {
4439
        $setVisibility = (int) $setVisibility;
4440
        $addShortcut = false;
4441
        if (1 === $setVisibility) {
4442
            $addShortcut = true;
4443
        }
4444
4445
        $repo = Container::getLpCategoryRepository();
4446
        /** @var CLpCategory $lp */
4447
        $category = $repo->find($id);
4448
4449
        if (null === $category) {
4450
            return false;
4451
        }
4452
4453
        $repoShortcut = Container::getShortcutRepository();
4454
        if ($addShortcut) {
4455
            $courseEntity = api_get_course_entity(api_get_course_int_id());
4456
            $repoShortcut->addShortCut($category, $courseEntity, $courseEntity, api_get_session_entity());
4457
        } else {
4458
            $repoShortcut->removeShortCut($category);
4459
        }
4460
4461
        return true;
4462
4463
        $em = Database::getManager();
0 ignored issues
show
Unused Code introduced by
$em = Database::getManager() 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...
4464
4465
        /** @var CLpCategory $category */
4466
        $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
4467
4468
        if (!$category) {
4469
            return false;
4470
        }
4471
4472
        if (empty($courseId)) {
4473
            return false;
4474
        }
4475
4476
        $link = self::getCategoryLinkForTool($id);
4477
4478
        /** @var CTool $tool */
4479
        $tool = $em->createQuery("
4480
                SELECT t FROM ChamiloCourseBundle:CTool t
4481
                WHERE
4482
                    t.course = :course AND
4483
                    t.link = :link1 AND
4484
                    t.image LIKE 'lp_category.%' AND
4485
                    t.link LIKE :link2
4486
                    $sessionCondition
4487
            ")
4488
            ->setParameters([
4489
                'course' => $courseId,
4490
                'link1' => $link,
4491
                'link2' => "$link%",
4492
            ])
4493
            ->getOneOrNullResult();
4494
4495
        if (0 == $setVisibility && $tool) {
4496
            $em->remove($tool);
4497
            $em->flush();
4498
4499
            return true;
4500
        }
4501
4502
        if (1 == $setVisibility && !$tool) {
4503
            $tool = new CTool();
4504
            $tool
4505
                ->setCategory('authoring')
4506
                ->setCourse(api_get_course_entity($courseId))
4507
                ->setName(strip_tags($category->getName()))
4508
                ->setLink($link)
4509
                ->setImage('lp_category.png')
4510
                ->setVisibility(1)
4511
                ->setAdmin(0)
4512
                ->setAddress('pastillegris.gif')
4513
                ->setAddedTool(0)
4514
                ->setSessionId($sessionId)
4515
                ->setTarget('_self');
4516
4517
            $em->persist($tool);
4518
            $em->flush();
4519
4520
            $tool->setId($tool->getIid());
4521
4522
            $em->persist($tool);
4523
            $em->flush();
4524
4525
            return true;
4526
        }
4527
4528
        if (1 == $setVisibility && $tool) {
4529
            $tool
4530
                ->setName(strip_tags($category->getName()))
4531
                ->setVisibility(1);
4532
4533
            $em->persist($tool);
4534
            $em->flush();
4535
4536
            return true;
4537
        }
4538
4539
        return false;
4540
    }
4541
4542
    /**
4543
     * Check if the learnpath category is visible for a user.
4544
     *
4545
     * @param int
4546
     * @param int
4547
     *
4548
     * @return bool
4549
     */
4550
    public static function categoryIsVisibleForStudent(
4551
        CLpCategory $category,
4552
        User $user,
4553
        $courseId = 0,
4554
        $sessionId = 0
4555
    ) {
4556
        if (empty($category)) {
4557
            return false;
4558
        }
4559
4560
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4561
4562
        if ($isAllowedToEdit) {
4563
            return true;
4564
        }
4565
4566
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4567
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4568
4569
        $courseInfo = api_get_course_info_by_id($courseId);
4570
4571
        $categoryVisibility = api_get_item_visibility(
4572
            $courseInfo,
4573
            TOOL_LEARNPATH_CATEGORY,
4574
            $category->getId(),
4575
            $sessionId
4576
        );
4577
4578
        if (1 !== $categoryVisibility && -1 != $categoryVisibility) {
4579
            return false;
4580
        }
4581
4582
        $subscriptionSettings = self::getSubscriptionSettings();
4583
4584
        if (false == $subscriptionSettings['allow_add_users_to_lp_category']) {
4585
            return true;
4586
        }
4587
4588
        $noUserSubscribed = false;
4589
        $noGroupSubscribed = true;
4590
        $users = $category->getUsers();
4591
        if (empty($users) || !$users->count()) {
4592
            $noUserSubscribed = true;
4593
        } elseif ($category->hasUserAdded($user)) {
4594
            return true;
4595
        }
4596
4597
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4598
        $em = Database::getManager();
4599
4600
        /** @var ItemPropertyRepository $itemRepo */
4601
        $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4602
4603
        /** @var CourseRepository $courseRepo */
4604
        $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4605
        $session = null;
4606
        if (!empty($sessionId)) {
4607
            $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4608
        }
4609
4610
        $course = $courseRepo->find($courseId);
4611
4612
        if (0 != $courseId) {
4613
            // Subscribed groups to a LP
4614
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4615
                    TOOL_LEARNPATH_CATEGORY,
4616
                    $category->getId(),
4617
                    $course,
4618
                    $session
4619
                );
4620
        }
4621
4622
        if (!empty($subscribedGroupsInLp)) {
4623
            $noGroupSubscribed = false;
4624
            if (!empty($groups)) {
4625
                $groups = array_column($groups, 'iid');
4626
                /** @var CItemProperty $item */
4627
                foreach ($subscribedGroupsInLp as $item) {
4628
                    if ($item->getGroup() &&
4629
                        in_array($item->getGroup()->getId(), $groups)
4630
                    ) {
4631
                        return true;
4632
                    }
4633
                }
4634
            }
4635
        }
4636
        $response = $noGroupSubscribed && $noUserSubscribed;
4637
4638
        return $response;
4639
    }
4640
4641
    /**
4642
     * Check if a learnpath category is published as course tool.
4643
     *
4644
     * @param int $courseId
4645
     *
4646
     * @return bool
4647
     */
4648
    public static function categoryIsPublished(CLpCategory $category, $courseId)
4649
    {
4650
        return false;
4651
        $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...
4652
        $em = Database::getManager();
4653
4654
        $tools = $em
4655
            ->createQuery("
4656
                SELECT t FROM ChamiloCourseBundle:CTool t
4657
                WHERE t.course = :course AND
4658
                    t.name = :name AND
4659
                    t.image LIKE 'lp_category.%' AND
4660
                    t.link LIKE :link
4661
            ")
4662
            ->setParameters([
4663
                'course' => $courseId,
4664
                'name' => strip_tags($category->getName()),
4665
                'link' => "$link%",
4666
            ])
4667
            ->getResult();
4668
4669
        /** @var CTool $tool */
4670
        $tool = current($tools);
4671
4672
        return $tool ? $tool->getVisibility() : false;
4673
    }
4674
4675
    /**
4676
     * Restart the whole learnpath. Return the URL of the first element.
4677
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4678
     * To use a similar method  statically, use the create_new_attempt() method.
4679
     *
4680
     * @return bool
4681
     */
4682
    public function restart()
4683
    {
4684
        if ($this->debug > 0) {
4685
            error_log('In learnpath::restart()', 0);
4686
        }
4687
        // TODO
4688
        // Call autosave method to save the current progress.
4689
        //$this->index = 0;
4690
        if (api_is_invitee()) {
4691
            return false;
4692
        }
4693
        $session_id = api_get_session_id();
4694
        $course_id = api_get_course_int_id();
4695
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4696
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4697
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4698
        if ($this->debug > 2) {
4699
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4700
        }
4701
        Database::query($sql);
4702
        $view_id = Database::insert_id();
4703
4704
        if ($view_id) {
4705
            $this->lp_view_id = $view_id;
4706
            $this->attempt = $this->attempt + 1;
4707
        } else {
4708
            $this->error = 'Could not insert into item_view table...';
4709
4710
            return false;
4711
        }
4712
        $this->autocomplete_parents($this->current);
4713
        foreach ($this->items as $index => $dummy) {
4714
            $this->items[$index]->restart();
4715
            $this->items[$index]->set_lp_view($this->lp_view_id);
4716
        }
4717
        $this->first();
4718
4719
        return true;
4720
    }
4721
4722
    /**
4723
     * Saves the current item.
4724
     *
4725
     * @return bool
4726
     */
4727
    public function save_current()
4728
    {
4729
        $debug = $this->debug;
4730
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4731
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4732
        if ($debug) {
4733
            error_log('save_current() saving item '.$this->current, 0);
4734
            error_log(''.print_r($this->items, true), 0);
4735
        }
4736
        if (isset($this->items[$this->current]) &&
4737
            is_object($this->items[$this->current])
4738
        ) {
4739
            if ($debug) {
4740
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4741
            }
4742
4743
            $res = $this->items[$this->current]->save(
4744
                false,
4745
                $this->prerequisites_match($this->current)
4746
            );
4747
            $this->autocomplete_parents($this->current);
4748
            $status = $this->items[$this->current]->get_status();
4749
            $this->update_queue[$this->current] = $status;
4750
4751
            if ($debug) {
4752
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4753
            }
4754
4755
            return $res;
4756
        }
4757
4758
        return false;
4759
    }
4760
4761
    /**
4762
     * Saves the given item.
4763
     *
4764
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4765
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4766
     *
4767
     * @return bool
4768
     */
4769
    public function save_item($item_id = null, $from_outside = true)
4770
    {
4771
        $debug = $this->debug;
4772
        if ($debug) {
4773
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4774
        }
4775
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4776
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4777
        if (empty($item_id)) {
4778
            $item_id = (int) $_REQUEST['id'];
4779
        }
4780
4781
        if (empty($item_id)) {
4782
            $item_id = $this->get_current_item_id();
4783
        }
4784
        if (isset($this->items[$item_id]) &&
4785
            is_object($this->items[$item_id])
4786
        ) {
4787
            if ($debug) {
4788
                error_log('Object exists');
4789
            }
4790
4791
            // Saving the item.
4792
            $res = $this->items[$item_id]->save(
4793
                $from_outside,
4794
                $this->prerequisites_match($item_id)
4795
            );
4796
4797
            if ($debug) {
4798
                error_log('update_queue before:');
4799
                error_log(print_r($this->update_queue, 1));
4800
            }
4801
            $this->autocomplete_parents($item_id);
4802
4803
            $status = $this->items[$item_id]->get_status();
4804
            $this->update_queue[$item_id] = $status;
4805
4806
            if ($debug) {
4807
                error_log('get_status(): '.$status);
4808
                error_log('update_queue after:');
4809
                error_log(print_r($this->update_queue, 1));
4810
            }
4811
4812
            return $res;
4813
        }
4814
4815
        return false;
4816
    }
4817
4818
    /**
4819
     * Saves the last item seen's ID only in case.
4820
     */
4821
    public function save_last()
4822
    {
4823
        $course_id = api_get_course_int_id();
4824
        $debug = $this->debug;
4825
        if ($debug) {
4826
            error_log('In learnpath::save_last()', 0);
4827
        }
4828
        $session_condition = api_get_session_condition(
4829
            api_get_session_id(),
4830
            true,
4831
            false
4832
        );
4833
        $table = Database::get_course_table(TABLE_LP_VIEW);
4834
4835
        $userId = $this->get_user_id();
4836
        if (empty($userId)) {
4837
            $userId = api_get_user_id();
4838
            if ($debug) {
4839
                error_log('$this->get_user_id() was empty, used api_get_user_id() instead in '.__FILE__.' line '.__LINE__);
4840
            }
4841
        }
4842
        if (isset($this->current) && !api_is_invitee()) {
4843
            if ($debug) {
4844
                error_log('Saving current item ('.$this->current.') for later review', 0);
4845
            }
4846
            $sql = "UPDATE $table SET
4847
                        last_item = ".$this->get_current_item_id()."
4848
                    WHERE
4849
                        c_id = $course_id AND
4850
                        lp_id = ".$this->get_id()." AND
4851
                        user_id = ".$userId." ".$session_condition;
4852
4853
            if ($debug) {
4854
                error_log('Saving last item seen : '.$sql, 0);
4855
            }
4856
            Database::query($sql);
4857
        }
4858
4859
        if (!api_is_invitee()) {
4860
            // Save progress.
4861
            [$progress] = $this->get_progress_bar_text('%');
4862
            $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
4863
            $scoreAsProgress = $this->getUseScoreAsProgress();
4864
            if ($scoreAsProgress && $scoreAsProgressSetting && (null === $score || empty($score) || -1 == $score)) {
4865
                if ($debug) {
4866
                    error_log("Return false: Dont save score: $score");
4867
                    error_log("progress: $progress");
4868
                }
4869
4870
                return false;
4871
            }
4872
4873
            if ($scoreAsProgress && $scoreAsProgressSetting) {
4874
                $storedProgress = self::getProgress(
4875
                    $this->get_id(),
4876
                    $userId,
4877
                    $course_id,
4878
                    $this->get_lp_session_id()
4879
                );
4880
4881
                // Check if the stored progress is higher than the new value
4882
                if ($storedProgress >= $progress) {
4883
                    if ($debug) {
4884
                        error_log("Return false: New progress value is lower than stored value - Current value: $storedProgress - New value: $progress [lp ".$this->get_id()." - user ".$userId."]");
4885
                    }
4886
4887
                    return false;
4888
                }
4889
            }
4890
            if ($progress >= 0 && $progress <= 100) {
4891
                $progress = (int) $progress;
4892
                $sql = "UPDATE $table SET
4893
                            progress = $progress
4894
                        WHERE
4895
                            c_id = $course_id AND
4896
                            lp_id = ".$this->get_id()." AND
4897
                            user_id = ".$userId." ".$session_condition;
4898
                // Ignore errors as some tables might not have the progress field just yet.
4899
                Database::query($sql);
4900
                $this->progress_db = $progress;
4901
            }
4902
        }
4903
    }
4904
4905
    /**
4906
     * Sets the current item ID (checks if valid and authorized first).
4907
     *
4908
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
4909
     */
4910
    public function set_current_item($item_id = null)
4911
    {
4912
        $debug = $this->debug;
4913
        if ($debug) {
4914
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
4915
        }
4916
        if (empty($item_id)) {
4917
            if ($debug) {
4918
                error_log('No new current item given, ignore...', 0);
4919
            }
4920
            // Do nothing.
4921
        } else {
4922
            if ($debug) {
4923
                error_log('New current item given is '.$item_id.'...', 0);
4924
            }
4925
            if (is_numeric($item_id)) {
4926
                $item_id = (int) $item_id;
4927
                // TODO: Check in database here.
4928
                $this->last = $this->current;
4929
                $this->current = $item_id;
4930
                // TODO: Update $this->index as well.
4931
                foreach ($this->ordered_items as $index => $item) {
4932
                    if ($item == $this->current) {
4933
                        $this->index = $index;
4934
                        break;
4935
                    }
4936
                }
4937
                if ($debug) {
4938
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
4939
                }
4940
            } else {
4941
                if ($debug) {
4942
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
4943
                }
4944
            }
4945
        }
4946
    }
4947
4948
    /**
4949
     * Sets the encoding.
4950
     *
4951
     * @param string $enc New encoding
4952
     *
4953
     * @return bool
4954
     *
4955
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
4956
     */
4957
    public function set_encoding($enc = 'UTF-8')
4958
    {
4959
        $enc = api_refine_encoding_id($enc);
4960
        if (empty($enc)) {
4961
            $enc = api_get_system_encoding();
4962
        }
4963
        if (api_is_encoding_supported($enc)) {
4964
            $lp = $this->get_id();
4965
            if (0 != $lp) {
4966
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4967
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
4968
                        WHERE iid = ".$lp;
4969
                $res = Database::query($sql);
4970
4971
                return $res;
4972
            }
4973
        }
4974
4975
        return false;
4976
    }
4977
4978
    /**
4979
     * Sets the JS lib setting in the database directly.
4980
     * This is the JavaScript library file this lp needs to load on startup.
4981
     *
4982
     * @param string $lib Proximity setting
4983
     *
4984
     * @return bool True on update success. False otherwise.
4985
     */
4986
    public function set_jslib($lib = '')
4987
    {
4988
        $lp = $this->get_id();
4989
4990
        if (0 != $lp) {
4991
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4992
            $lib = Database::escape_string($lib);
4993
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
4994
                    WHERE iid = $lp";
4995
            $res = Database::query($sql);
4996
4997
            return $res;
4998
        }
4999
5000
        return false;
5001
    }
5002
5003
    /**
5004
     * Set index specified prefix terms for all items in this path.
5005
     *
5006
     * @param string $terms_string Comma-separated list of terms
5007
     * @param string $prefix       Xapian term prefix
5008
     *
5009
     * @return bool False on error, true otherwise
5010
     */
5011
    public function set_terms_by_prefix($terms_string, $prefix)
5012
    {
5013
        $course_id = api_get_course_int_id();
5014
        if ('true' !== api_get_setting('search_enabled')) {
5015
            return false;
5016
        }
5017
5018
        if (!extension_loaded('xapian')) {
5019
            return false;
5020
        }
5021
5022
        $terms_string = trim($terms_string);
5023
        $terms = explode(',', $terms_string);
5024
        array_walk($terms, 'trim_value');
5025
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5026
5027
        // Don't do anything if no change, verify only at DB, not the search engine.
5028
        if ((0 == count(array_diff($terms, $stored_terms))) && (0 == count(array_diff($stored_terms, $terms)))) {
5029
            return false;
5030
        }
5031
5032
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5033
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5034
5035
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5036
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5037
        $lp_id = (int) $_POST['lp_id'];
5038
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5039
        $result = Database::query($sql);
5040
        $di = new ChamiloIndexer();
5041
5042
        while ($lp_item = Database::fetch_array($result)) {
5043
            // Get search_did.
5044
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5045
            $sql = 'SELECT * FROM %s
5046
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5047
                    LIMIT 1';
5048
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5049
5050
            //echo $sql; echo '<br>';
5051
            $res = Database::query($sql);
5052
            if (Database::num_rows($res) > 0) {
5053
                $se_ref = Database::fetch_array($res);
5054
                // Compare terms.
5055
                $doc = $di->get_document($se_ref['search_did']);
5056
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5057
                $xterms = [];
5058
                foreach ($xapian_terms as $xapian_term) {
5059
                    $xterms[] = substr($xapian_term['name'], 1);
5060
                }
5061
5062
                $dterms = $terms;
5063
                $missing_terms = array_diff($dterms, $xterms);
5064
                $deprecated_terms = array_diff($xterms, $dterms);
5065
5066
                // Save it to search engine.
5067
                foreach ($missing_terms as $term) {
5068
                    $doc->add_term($prefix.$term, 1);
5069
                }
5070
                foreach ($deprecated_terms as $term) {
5071
                    $doc->remove_term($prefix.$term);
5072
                }
5073
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5074
                $di->getDb()->flush();
5075
            }
5076
        }
5077
5078
        return true;
5079
    }
5080
5081
    /**
5082
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5083
     *
5084
     * @param int $id DB ID of the item
5085
     */
5086
    public function set_previous_item($id)
5087
    {
5088
        if ($this->debug > 0) {
5089
            error_log('In learnpath::set_previous_item()', 0);
5090
        }
5091
        $this->last = $id;
5092
    }
5093
5094
    /**
5095
     * Sets use_max_score.
5096
     *
5097
     * @param int $use_max_score Optional string giving the new location of this learnpath
5098
     *
5099
     * @return bool True on success / False on error
5100
     */
5101
    public function set_use_max_score($use_max_score = 1)
5102
    {
5103
        $use_max_score = (int) $use_max_score;
5104
        $this->use_max_score = $use_max_score;
5105
        $table = Database::get_course_table(TABLE_LP_MAIN);
5106
        $lp_id = $this->get_id();
5107
        $sql = "UPDATE $table SET
5108
                    use_max_score = '".$this->use_max_score."'
5109
                WHERE iid = $lp_id";
5110
        Database::query($sql);
5111
5112
        return true;
5113
    }
5114
5115
    /**
5116
     * Sets and saves the expired_on date.
5117
     *
5118
     * @return bool Returns true if author's name is not empty
5119
     */
5120
    public function set_modified_on()
5121
    {
5122
        $this->modified_on = api_get_utc_datetime();
5123
        $table = Database::get_course_table(TABLE_LP_MAIN);
5124
        $lp_id = $this->get_id();
5125
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5126
                WHERE iid = $lp_id";
5127
        Database::query($sql);
5128
5129
        return true;
5130
    }
5131
5132
    /**
5133
     * Sets the object's error message.
5134
     *
5135
     * @param string $error Error message. If empty, reinits the error string
5136
     */
5137
    public function set_error_msg($error = '')
5138
    {
5139
        if ($this->debug > 0) {
5140
            error_log('In learnpath::set_error_msg()', 0);
5141
        }
5142
        if (empty($error)) {
5143
            $this->error = '';
5144
        } else {
5145
            $this->error .= $error;
5146
        }
5147
    }
5148
5149
    /**
5150
     * Launches the current item if not 'sco'
5151
     * (starts timer and make sure there is a record ready in the DB).
5152
     *
5153
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5154
     *
5155
     * @return bool
5156
     */
5157
    public function start_current_item($allow_new_attempt = false)
5158
    {
5159
        $debug = $this->debug;
5160
        if ($debug) {
5161
            error_log('In learnpath::start_current_item()');
5162
            error_log('current: '.$this->current);
5163
        }
5164
        if (0 != $this->current && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5165
            $type = $this->get_type();
5166
            $item_type = $this->items[$this->current]->get_type();
5167
            if ((2 == $type && 'sco' != $item_type) ||
5168
                (3 == $type && 'au' != $item_type) ||
5169
                (1 == $type && TOOL_QUIZ != $item_type && TOOL_HOTPOTATOES != $item_type)
5170
            ) {
5171
                if ($debug) {
5172
                    error_log('item type: '.$item_type);
5173
                    error_log('lp type: '.$type);
5174
                }
5175
                $this->items[$this->current]->open($allow_new_attempt);
5176
                $this->autocomplete_parents($this->current);
5177
                $prereq_check = $this->prerequisites_match($this->current);
5178
                if ($debug) {
5179
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5180
                }
5181
                $this->items[$this->current]->save(false, $prereq_check);
5182
            }
5183
            // If sco, then it is supposed to have been updated by some other call.
5184
            if ('sco' == $item_type) {
5185
                $this->items[$this->current]->restart();
5186
            }
5187
        }
5188
        if ($debug) {
5189
            error_log('lp_view_session_id');
5190
            error_log($this->lp_view_session_id);
5191
            error_log('api session id');
5192
            error_log(api_get_session_id());
5193
            error_log('End of learnpath::start_current_item()');
5194
        }
5195
5196
        return true;
5197
    }
5198
5199
    /**
5200
     * Stops the processing and counters for the old item (as held in $this->last).
5201
     *
5202
     * @return bool True/False
5203
     */
5204
    public function stop_previous_item()
5205
    {
5206
        $debug = $this->debug;
5207
        if ($debug) {
5208
            error_log('In learnpath::stop_previous_item()', 0);
5209
        }
5210
5211
        if (0 != $this->last && $this->last != $this->current &&
5212
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5213
        ) {
5214
            if ($debug) {
5215
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5216
            }
5217
            switch ($this->get_type()) {
5218
                case '3':
5219
                    if ('au' != $this->items[$this->last]->get_type()) {
5220
                        if ($debug) {
5221
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5222
                        }
5223
                        $this->items[$this->last]->close();
5224
                    } else {
5225
                        if ($debug) {
5226
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5227
                        }
5228
                    }
5229
                    break;
5230
                case '2':
5231
                    if ('sco' != $this->items[$this->last]->get_type()) {
5232
                        if ($debug) {
5233
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5234
                        }
5235
                        $this->items[$this->last]->close();
5236
                    } else {
5237
                        if ($debug) {
5238
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5239
                        }
5240
                    }
5241
                    break;
5242
                case '1':
5243
                default:
5244
                    if ($debug) {
5245
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5246
                    }
5247
                    $this->items[$this->last]->close();
5248
                    break;
5249
            }
5250
        } else {
5251
            if ($debug) {
5252
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5253
            }
5254
5255
            return false;
5256
        }
5257
5258
        return true;
5259
    }
5260
5261
    /**
5262
     * Updates the default view mode from fullscreen to embedded and inversely.
5263
     *
5264
     * @return string The current default view mode ('fullscreen' or 'embedded')
5265
     */
5266
    public function update_default_view_mode()
5267
    {
5268
        $table = Database::get_course_table(TABLE_LP_MAIN);
5269
        $sql = "SELECT * FROM $table
5270
                WHERE iid = ".$this->get_id();
5271
        $res = Database::query($sql);
5272
        if (Database::num_rows($res) > 0) {
5273
            $row = Database::fetch_array($res);
5274
            $default_view_mode = $row['default_view_mod'];
5275
            $view_mode = $default_view_mode;
5276
            switch ($default_view_mode) {
5277
                case 'fullscreen': // default with popup
5278
                    $view_mode = 'embedded';
5279
                    break;
5280
                case 'embedded': // default view with left menu
5281
                    $view_mode = 'embedframe';
5282
                    break;
5283
                case 'embedframe': //folded menu
5284
                    $view_mode = 'impress';
5285
                    break;
5286
                case 'impress':
5287
                    $view_mode = 'fullscreen';
5288
                    break;
5289
            }
5290
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5291
                    WHERE iid = ".$this->get_id();
5292
            Database::query($sql);
5293
            $this->mode = $view_mode;
5294
5295
            return $view_mode;
5296
        }
5297
5298
        return -1;
5299
    }
5300
5301
    /**
5302
     * Updates the default behaviour about auto-commiting SCORM updates.
5303
     *
5304
     * @return bool True if auto-commit has been set to 'on', false otherwise
5305
     */
5306
    public function update_default_scorm_commit()
5307
    {
5308
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5309
        $sql = "SELECT * FROM $lp_table
5310
                WHERE iid = ".$this->get_id();
5311
        $res = Database::query($sql);
5312
        if (Database::num_rows($res) > 0) {
5313
            $row = Database::fetch_array($res);
5314
            $force = $row['force_commit'];
5315
            if (1 == $force) {
5316
                $force = 0;
5317
                $force_return = false;
5318
            } elseif (0 == $force) {
5319
                $force = 1;
5320
                $force_return = true;
5321
            }
5322
            $sql = "UPDATE $lp_table SET force_commit = $force
5323
                    WHERE iid = ".$this->get_id();
5324
            Database::query($sql);
5325
            $this->force_commit = $force_return;
5326
5327
            return $force_return;
5328
        }
5329
5330
        return -1;
5331
    }
5332
5333
    /**
5334
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5335
     *
5336
     * @return bool True on success, false on failure
5337
     */
5338
    public function update_display_order()
5339
    {
5340
        $course_id = api_get_course_int_id();
5341
        $table = Database::get_course_table(TABLE_LP_MAIN);
5342
        $sql = "SELECT * FROM $table
5343
                WHERE c_id = $course_id
5344
                ORDER BY display_order";
5345
        $res = Database::query($sql);
5346
        if (false === $res) {
5347
            return false;
5348
        }
5349
5350
        $num = Database::num_rows($res);
5351
        // First check the order is correct, globally (might be wrong because
5352
        // of versions < 1.8.4).
5353
        if ($num > 0) {
5354
            $i = 1;
5355
            while ($row = Database::fetch_array($res)) {
5356
                if ($row['display_order'] != $i) {
5357
                    // If we find a gap in the order, we need to fix it.
5358
                    $sql = "UPDATE $table SET display_order = $i
5359
                            WHERE iid = ".$row['iid'];
5360
                    Database::query($sql);
5361
                }
5362
                $i++;
5363
            }
5364
        }
5365
5366
        return true;
5367
    }
5368
5369
    /**
5370
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5371
     *
5372
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5373
     */
5374
    public function update_reinit()
5375
    {
5376
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5377
        $sql = "SELECT * FROM $lp_table
5378
                WHERE iid = ".$this->get_id();
5379
        $res = Database::query($sql);
5380
        if (Database::num_rows($res) > 0) {
5381
            $row = Database::fetch_array($res);
5382
            $force = $row['prevent_reinit'];
5383
            if (1 == $force) {
5384
                $force = 0;
5385
            } elseif (0 == $force) {
5386
                $force = 1;
5387
            }
5388
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5389
                    WHERE iid = ".$this->get_id();
5390
            Database::query($sql);
5391
            $this->prevent_reinit = $force;
5392
5393
            return $force;
5394
        }
5395
5396
        return -1;
5397
    }
5398
5399
    /**
5400
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5401
     *
5402
     * @return string 'single', 'multi' or 'seriousgame'
5403
     *
5404
     * @author ndiechburg <[email protected]>
5405
     */
5406
    public function get_attempt_mode()
5407
    {
5408
        //Set default value for seriousgame_mode
5409
        if (!isset($this->seriousgame_mode)) {
5410
            $this->seriousgame_mode = 0;
5411
        }
5412
        // Set default value for prevent_reinit
5413
        if (!isset($this->prevent_reinit)) {
5414
            $this->prevent_reinit = 1;
5415
        }
5416
        if (1 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
5417
            return 'seriousgame';
5418
        }
5419
        if (0 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
5420
            return 'single';
5421
        }
5422
        if (0 == $this->seriousgame_mode && 0 == $this->prevent_reinit) {
5423
            return 'multiple';
5424
        }
5425
5426
        return 'single';
5427
    }
5428
5429
    /**
5430
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5431
     *
5432
     * @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...
5433
     *
5434
     * @return bool
5435
     *
5436
     * @author ndiechburg <[email protected]>
5437
     */
5438
    public function set_attempt_mode($mode)
5439
    {
5440
        switch ($mode) {
5441
            case 'seriousgame':
5442
                $sg_mode = 1;
5443
                $prevent_reinit = 1;
5444
                break;
5445
            case 'single':
5446
                $sg_mode = 0;
5447
                $prevent_reinit = 1;
5448
                break;
5449
            case 'multiple':
5450
                $sg_mode = 0;
5451
                $prevent_reinit = 0;
5452
                break;
5453
            default:
5454
                $sg_mode = 0;
5455
                $prevent_reinit = 0;
5456
                break;
5457
        }
5458
        $this->prevent_reinit = $prevent_reinit;
5459
        $this->seriousgame_mode = $sg_mode;
5460
        $table = Database::get_course_table(TABLE_LP_MAIN);
5461
        $sql = "UPDATE $table SET
5462
                prevent_reinit = $prevent_reinit ,
5463
                seriousgame_mode = $sg_mode
5464
                WHERE iid = ".$this->get_id();
5465
        $res = Database::query($sql);
5466
        if ($res) {
5467
            return true;
5468
        } else {
5469
            return false;
5470
        }
5471
    }
5472
5473
    /**
5474
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5475
     *
5476
     * @author ndiechburg <[email protected]>
5477
     */
5478
    public function switch_attempt_mode()
5479
    {
5480
        $mode = $this->get_attempt_mode();
5481
        switch ($mode) {
5482
            case 'single':
5483
                $next_mode = 'multiple';
5484
                break;
5485
            case 'multiple':
5486
                $next_mode = 'seriousgame';
5487
                break;
5488
            case 'seriousgame':
5489
            default:
5490
                $next_mode = 'single';
5491
                break;
5492
        }
5493
        $this->set_attempt_mode($next_mode);
5494
    }
5495
5496
    /**
5497
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5498
     * but possibility to do again a completed item.
5499
     *
5500
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5501
     *
5502
     * @author ndiechburg <[email protected]>
5503
     */
5504
    public function set_seriousgame_mode()
5505
    {
5506
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5507
        $sql = "SELECT * FROM $lp_table
5508
                WHERE iid = ".$this->get_id();
5509
        $res = Database::query($sql);
5510
        if (Database::num_rows($res) > 0) {
5511
            $row = Database::fetch_array($res);
5512
            $force = $row['seriousgame_mode'];
5513
            if (1 == $force) {
5514
                $force = 0;
5515
            } elseif (0 == $force) {
5516
                $force = 1;
5517
            }
5518
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5519
			        WHERE iid = ".$this->get_id();
5520
            Database::query($sql);
5521
            $this->seriousgame_mode = $force;
5522
5523
            return $force;
5524
        }
5525
5526
        return -1;
5527
    }
5528
5529
    /**
5530
     * Updates the "scorm_debug" value that shows or hide the debug window.
5531
     *
5532
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5533
     */
5534
    public function update_scorm_debug()
5535
    {
5536
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5537
        $sql = "SELECT * FROM $lp_table
5538
                WHERE iid = ".$this->get_id();
5539
        $res = Database::query($sql);
5540
        if (Database::num_rows($res) > 0) {
5541
            $row = Database::fetch_array($res);
5542
            $force = $row['debug'];
5543
            if (1 == $force) {
5544
                $force = 0;
5545
            } elseif (0 == $force) {
5546
                $force = 1;
5547
            }
5548
            $sql = "UPDATE $lp_table SET debug = $force
5549
                    WHERE iid = ".$this->get_id();
5550
            Database::query($sql);
5551
            $this->scorm_debug = $force;
5552
5553
            return $force;
5554
        }
5555
5556
        return -1;
5557
    }
5558
5559
    /**
5560
     * Function that makes a call to the function sort_tree_array and create_tree_array.
5561
     *
5562
     * @author Kevin Van Den Haute
5563
     *
5564
     * @param  array
5565
     */
5566
    public function tree_array($array)
5567
    {
5568
        $array = $this->sort_tree_array($array);
5569
        $this->create_tree_array($array);
5570
    }
5571
5572
    /**
5573
     * Creates an array with the elements of the learning path tree in it.
5574
     *
5575
     * @author Kevin Van Den Haute
5576
     *
5577
     * @param array $array
5578
     * @param int   $parent
5579
     * @param int   $depth
5580
     * @param array $tmp
5581
     */
5582
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
5583
    {
5584
        if (is_array($array)) {
5585
            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...
5586
                if ($array[$i]['parent_item_id'] == $parent) {
5587
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
5588
                        $tmp[] = $array[$i]['parent_item_id'];
5589
                        $depth++;
5590
                    }
5591
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
5592
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
5593
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
5594
5595
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
5596
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
5597
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
5598
                    $this->arrMenu[] = [
5599
                        'id' => $array[$i]['id'],
5600
                        'ref' => $ref,
5601
                        'item_type' => $array[$i]['item_type'],
5602
                        'title' => $array[$i]['title'],
5603
                        'title_raw' => $array[$i]['title_raw'],
5604
                        'path' => $path,
5605
                        'description' => $array[$i]['description'],
5606
                        'parent_item_id' => $array[$i]['parent_item_id'],
5607
                        'previous_item_id' => $array[$i]['previous_item_id'],
5608
                        'next_item_id' => $array[$i]['next_item_id'],
5609
                        'min_score' => $array[$i]['min_score'],
5610
                        'max_score' => $array[$i]['max_score'],
5611
                        'mastery_score' => $array[$i]['mastery_score'],
5612
                        'display_order' => $array[$i]['display_order'],
5613
                        'prerequisite' => $preq,
5614
                        'depth' => $depth,
5615
                        'audio' => $audio,
5616
                        'prerequisite_min_score' => $prerequisiteMinScore,
5617
                        'prerequisite_max_score' => $prerequisiteMaxScore,
5618
                    ];
5619
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
5620
                }
5621
            }
5622
        }
5623
    }
5624
5625
    /**
5626
     * Sorts a multi dimensional array by parent id and display order.
5627
     *
5628
     * @author Kevin Van Den Haute
5629
     *
5630
     * @param array $array (array with al the learning path items in it)
5631
     *
5632
     * @return array
5633
     */
5634
    public function sort_tree_array($array)
5635
    {
5636
        foreach ($array as $key => $row) {
5637
            $parent[$key] = $row['parent_item_id'];
5638
            $position[$key] = $row['display_order'];
5639
        }
5640
5641
        if (count($array) > 0) {
5642
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
5643
        }
5644
5645
        return $array;
5646
    }
5647
5648
    /**
5649
     * Function that creates a html list of learning path items so that we can add audio files to them.
5650
     *
5651
     * @author Kevin Van Den Haute
5652
     *
5653
     * @return string
5654
     */
5655
    public function overview()
5656
    {
5657
        $return = '';
5658
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
5659
5660
        // we need to start a form when we want to update all the mp3 files
5661
        if ('true' == $update_audio) {
5662
            $return .= '<form action="'.api_get_self().'?'.api_get_cidreq().'&updateaudio='.Security::remove_XSS($_GET['updateaudio']).'&action='.Security::remove_XSS($_GET['action']).'&lp_id='.$_SESSION['oLP']->lp_id.'" method="post" enctype="multipart/form-data" name="updatemp3" id="updatemp3">';
5663
        }
5664
        $return .= '<div id="message"></div>';
5665
        if (0 == count($this->items)) {
5666
            $return .= Display::return_message(get_lang('You should add some items to your learning path, otherwise you won\'t be able to attach audio files to them'), 'normal');
5667
        } else {
5668
            $return_audio = '<table class="table table-hover table-striped data_table">';
5669
            $return_audio .= '<tr>';
5670
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
5671
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
5672
            $return_audio .= '</tr>';
5673
5674
            if ('true' != $update_audio) {
5675
                $return .= '<div class="col-md-12">';
5676
                $return .= self::return_new_tree($update_audio);
5677
                $return .= '</div>';
5678
                $return .= Display::div(
5679
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
5680
                    ['style' => 'float:left; margin-top:15px;width:100%']
5681
                );
5682
            } else {
5683
                $return_audio .= self::return_new_tree($update_audio);
5684
                $return .= $return_audio.'</table>';
5685
            }
5686
5687
            // We need to close the form when we are updating the mp3 files.
5688
            if ('true' == $update_audio) {
5689
                $return .= '<div class="footer-audio">';
5690
                $return .= Display::button(
5691
                    'save_audio',
5692
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('Save audio and organization'),
5693
                    ['class' => 'btn btn-primary', 'type' => 'submit']
5694
                );
5695
                $return .= '</div>';
5696
            }
5697
        }
5698
5699
        // We need to close the form when we are updating the mp3 files.
5700
        if ('true' == $update_audio && isset($this->arrMenu) && 0 != count($this->arrMenu)) {
5701
            $return .= '</form>';
5702
        }
5703
5704
        return $return;
5705
    }
5706
5707
    /**
5708
     * @param string $update_audio
5709
     *
5710
     * @return array
5711
     */
5712
    public function processBuildMenuElements($update_audio = 'false')
5713
    {
5714
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
5715
        $arrLP = $this->getItemsForForm();
5716
5717
        $this->tree_array($arrLP);
5718
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
5719
        unset($this->arrMenu);
5720
        $default_data = null;
5721
        $default_content = null;
5722
        $elements = [];
5723
        $return_audio = null;
5724
        $iconPath = api_get_path(SYS_PUBLIC_PATH).'img/';
5725
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
5726
        $countItems = count($arrLP);
5727
5728
        $upIcon = Display::return_icon(
5729
            'up.png',
5730
            get_lang('Up'),
5731
            [],
5732
            ICON_SIZE_TINY
5733
        );
5734
5735
        $disableUpIcon = Display::return_icon(
5736
            'up_na.png',
5737
            get_lang('Up'),
5738
            [],
5739
            ICON_SIZE_TINY
5740
        );
5741
5742
        $downIcon = Display::return_icon(
5743
            'down.png',
5744
            get_lang('Down'),
5745
            [],
5746
            ICON_SIZE_TINY
5747
        );
5748
5749
        $disableDownIcon = Display::return_icon(
5750
            'down_na.png',
5751
            get_lang('Down'),
5752
            [],
5753
            ICON_SIZE_TINY
5754
        );
5755
5756
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
5757
5758
        $pluginCalendar = 'true' === api_get_plugin_setting('learning_calendar', 'enabled');
5759
        $plugin = null;
5760
        if ($pluginCalendar) {
5761
            $plugin = LearningCalendarPlugin::create();
5762
        }
5763
5764
        for ($i = 0; $i < $countItems; $i++) {
5765
            $parent_id = $arrLP[$i]['parent_item_id'];
5766
            $title = $arrLP[$i]['title'];
5767
            $title_cut = $arrLP[$i]['title_raw'];
5768
            if (false === $show) {
5769
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
5770
            }
5771
            // Link for the documents
5772
            if ('document' === $arrLP[$i]['item_type'] || TOOL_READOUT_TEXT === $arrLP[$i]['item_type']) {
5773
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5774
                $title_cut = Display::url(
5775
                    $title_cut,
5776
                    $url,
5777
                    [
5778
                        'class' => 'ajax moved',
5779
                        'data-title' => $title,
5780
                        'title' => $title,
5781
                    ]
5782
                );
5783
            }
5784
5785
            // Detect if type is FINAL_ITEM to set path_id to SESSION
5786
            if (TOOL_LP_FINAL_ITEM === $arrLP[$i]['item_type']) {
5787
                Session::write('pathItem', $arrLP[$i]['path']);
5788
            }
5789
5790
            $oddClass = 'row_even';
5791
            if (0 == ($i % 2)) {
5792
                $oddClass = 'row_odd';
5793
            }
5794
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
5795
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
5796
5797
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
5798
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
5799
            } else {
5800
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
5801
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
5802
                } else {
5803
                    if (TOOL_LP_FINAL_ITEM === $arrLP[$i]['item_type']) {
5804
                        $icon = Display::return_icon('certificate.png');
5805
                    } else {
5806
                        $icon = Display::return_icon('folder_document.png');
5807
                    }
5808
                }
5809
            }
5810
5811
            // The audio column.
5812
            $return_audio .= '<td align="left" style="padding-left:10px;">';
5813
            $audio = '';
5814
            if (!$update_audio || 'true' != $update_audio) {
5815
                if (empty($arrLP[$i]['audio'])) {
5816
                    $audio .= '';
5817
                }
5818
            } else {
5819
                $types = self::getChapterTypes();
5820
                if (!in_array($arrLP[$i]['item_type'], $types)) {
5821
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
5822
                    if (!empty($arrLP[$i]['audio'])) {
5823
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
5824
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('Remove audio');
5825
                    }
5826
                }
5827
            }
5828
5829
            $return_audio .= Display::span($icon.' '.$title).
5830
                Display::tag(
5831
                    'td',
5832
                    $audio,
5833
                    ['style' => '']
5834
                );
5835
            $return_audio .= '</td>';
5836
            $move_icon = '';
5837
            $move_item_icon = '';
5838
            $edit_icon = '';
5839
            $delete_icon = '';
5840
            $audio_icon = '';
5841
            $prerequisities_icon = '';
5842
            $forumIcon = '';
5843
            $previewIcon = '';
5844
            $pluginCalendarIcon = '';
5845
            $orderIcons = '';
5846
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
5847
5848
            if ($is_allowed_to_edit) {
5849
                if (!$update_audio || 'true' != $update_audio) {
5850
                    if (TOOL_LP_FINAL_ITEM !== $arrLP[$i]['item_type']) {
5851
                        $move_icon .= '<a class="moved" href="#">';
5852
                        $move_icon .= Display::return_icon(
5853
                            'move_everywhere.png',
5854
                            get_lang('Move'),
5855
                            [],
5856
                            ICON_SIZE_TINY
5857
                        );
5858
                        $move_icon .= '</a>';
5859
                    }
5860
                }
5861
5862
                // No edit for this item types
5863
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
5864
                    if ('dir' != $arrLP[$i]['item_type']) {
5865
                        $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">';
5866
                        $edit_icon .= Display::return_icon(
5867
                            'edit.png',
5868
                            get_lang('Edit section description/name'),
5869
                            [],
5870
                            ICON_SIZE_TINY
5871
                        );
5872
                        $edit_icon .= '</a>';
5873
5874
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
5875
                            $forumThread = null;
5876
                            if (isset($this->items[$arrLP[$i]['id']])) {
5877
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
5878
                                    $this->course_int_id,
5879
                                    $this->lp_session_id
5880
                                );
5881
                            }
5882
                            if ($forumThread) {
5883
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
5884
                                        'action' => 'dissociate_forum',
5885
                                        'id' => $arrLP[$i]['id'],
5886
                                        'lp_id' => $this->lp_id,
5887
                                    ]);
5888
                                $forumIcon = Display::url(
5889
                                    Display::return_icon(
5890
                                        'forum.png',
5891
                                        get_lang('Dissociate the forum of this learning path item'),
5892
                                        [],
5893
                                        ICON_SIZE_TINY
5894
                                    ),
5895
                                    $forumIconUrl,
5896
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
5897
                                );
5898
                            } else {
5899
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
5900
                                        'action' => 'create_forum',
5901
                                        'id' => $arrLP[$i]['id'],
5902
                                        'lp_id' => $this->lp_id,
5903
                                    ]);
5904
                                $forumIcon = Display::url(
5905
                                    Display::return_icon(
5906
                                        'forum.png',
5907
                                        get_lang('Associate a forum to this learning path item'),
5908
                                        [],
5909
                                        ICON_SIZE_TINY
5910
                                    ),
5911
                                    $forumIconUrl,
5912
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
5913
                                );
5914
                            }
5915
                        }
5916
                    } else {
5917
                        $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">';
5918
                        $edit_icon .= Display::return_icon(
5919
                            'edit.png',
5920
                            get_lang('Edit section description/name'),
5921
                            [],
5922
                            ICON_SIZE_TINY
5923
                        );
5924
                        $edit_icon .= '</a>';
5925
                    }
5926
                } else {
5927
                    if (TOOL_LP_FINAL_ITEM == $arrLP[$i]['item_type']) {
5928
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
5929
                        $edit_icon .= Display::return_icon(
5930
                            'edit.png',
5931
                            get_lang('Edit'),
5932
                            [],
5933
                            ICON_SIZE_TINY
5934
                        );
5935
                        $edit_icon .= '</a>';
5936
                    }
5937
                }
5938
5939
                if ($pluginCalendar) {
5940
                    $pluginLink = $pluginUrl.
5941
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5942
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('1 day'), [], ICON_SIZE_TINY);
5943
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
5944
                    if ($itemInfo && 1 == $itemInfo['value']) {
5945
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('1 day'), [], ICON_SIZE_TINY);
5946
                    }
5947
                    $pluginCalendarIcon = Display::url(
5948
                        $iconCalendar,
5949
                        $pluginLink,
5950
                        ['class' => 'btn btn-default']
5951
                    );
5952
                }
5953
5954
                if ('final_item' != $arrLP[$i]['item_type']) {
5955
                    $orderIcons = Display::url(
5956
                        $upIcon,
5957
                        'javascript:void(0)',
5958
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
5959
                    );
5960
                    $orderIcons .= Display::url(
5961
                        $downIcon,
5962
                        'javascript:void(0)',
5963
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
5964
                    );
5965
                }
5966
5967
                $delete_icon .= ' <a
5968
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
5969
                    onclick="return confirmation(\''.addslashes($title).'\');"
5970
                    class="btn btn-default">';
5971
                $delete_icon .= Display::return_icon(
5972
                    'delete.png',
5973
                    get_lang('Delete section'),
5974
                    [],
5975
                    ICON_SIZE_TINY
5976
                );
5977
                $delete_icon .= '</a>';
5978
5979
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5980
                $previewImage = Display::return_icon(
5981
                    'preview_view.png',
5982
                    get_lang('Preview'),
5983
                    [],
5984
                    ICON_SIZE_TINY
5985
                );
5986
5987
                switch ($arrLP[$i]['item_type']) {
5988
                    case TOOL_DOCUMENT:
5989
                    case TOOL_LP_FINAL_ITEM:
5990
                    case TOOL_READOUT_TEXT:
5991
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5992
                        $previewIcon = Display::url(
5993
                            $previewImage,
5994
                            $urlPreviewLink,
5995
                            [
5996
                                'target' => '_blank',
5997
                                'class' => 'btn btn-default',
5998
                                'data-title' => $arrLP[$i]['title'],
5999
                                'title' => $arrLP[$i]['title'],
6000
                            ]
6001
                        );
6002
                        break;
6003
                    case TOOL_THREAD:
6004
                    case TOOL_FORUM:
6005
                    case TOOL_QUIZ:
6006
                    case TOOL_STUDENTPUBLICATION:
6007
                    case TOOL_LINK:
6008
                        $class = 'btn btn-default';
6009
                        $target = '_blank';
6010
                        $link = self::rl_get_resource_link_for_learnpath(
6011
                            $this->course_int_id,
6012
                            $this->lp_id,
6013
                            $arrLP[$i]['id'],
6014
                            0
6015
                        );
6016
                        $previewIcon = Display::url(
6017
                            $previewImage,
6018
                            $link,
6019
                            [
6020
                                'class' => $class,
6021
                                'data-title' => $arrLP[$i]['title'],
6022
                                'title' => $arrLP[$i]['title'],
6023
                                'target' => $target,
6024
                            ]
6025
                        );
6026
                        break;
6027
                    default:
6028
                        $previewIcon = Display::url(
6029
                            $previewImage,
6030
                            $url.'&action=view_item',
6031
                            ['class' => 'btn btn-default', 'target' => '_blank']
6032
                        );
6033
                        break;
6034
                }
6035
6036
                if ('dir' != $arrLP[$i]['item_type']) {
6037
                    $prerequisities_icon = Display::url(
6038
                        Display::return_icon(
6039
                            'accept.png',
6040
                            get_lang('Prerequisites'),
6041
                            [],
6042
                            ICON_SIZE_TINY
6043
                        ),
6044
                        $url.'&action=edit_item_prereq',
6045
                        ['class' => 'btn btn-default']
6046
                    );
6047
                    if ('final_item' != $arrLP[$i]['item_type']) {
6048
                        /*$move_item_icon = Display::url(
6049
                            Display::return_icon(
6050
                                'move.png',
6051
                                get_lang('Move'),
6052
                                [],
6053
                                ICON_SIZE_TINY
6054
                            ),
6055
                            $url.'&action=move_item',
6056
                            ['class' => 'btn btn-default']
6057
                        );*/
6058
                    }
6059
                    $audio_icon = Display::url(
6060
                        Display::return_icon(
6061
                            'audio.png',
6062
                            get_lang('Upload'),
6063
                            [],
6064
                            ICON_SIZE_TINY
6065
                        ),
6066
                        $url.'&action=add_audio',
6067
                        ['class' => 'btn btn-default']
6068
                    );
6069
                }
6070
            }
6071
            if ('true' != $update_audio) {
6072
                $row = $move_icon.' '.$icon.
6073
                    Display::span($title_cut).
6074
                    Display::tag(
6075
                        'div',
6076
                        "<div class=\"btn-group btn-group-xs\">
6077
                                    $previewIcon
6078
                                    $audio
6079
                                    $edit_icon
6080
                                    $pluginCalendarIcon
6081
                                    $forumIcon
6082
                                    $prerequisities_icon
6083
                                    $move_item_icon
6084
                                    $audio_icon
6085
                                    $orderIcons
6086
                                    $delete_icon
6087
                                </div>",
6088
                        ['class' => 'btn-toolbar button_actions']
6089
                    );
6090
            } else {
6091
                $row =
6092
                    Display::span($title.$icon).
6093
                    Display::span($audio, ['class' => 'button_actions']);
6094
            }
6095
6096
            $default_data[$arrLP[$i]['id']] = $row;
6097
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6098
6099
            if (empty($parent_id)) {
6100
                $elements[$arrLP[$i]['id']]['data'] = $row;
6101
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6102
            } else {
6103
                $parent_arrays = [];
6104
                if ($arrLP[$i]['depth'] > 1) {
6105
                    // Getting list of parents
6106
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6107
                        foreach ($arrLP as $item) {
6108
                            if ($item['id'] == $parent_id) {
6109
                                if (0 == $item['parent_item_id']) {
6110
                                    $parent_id = $item['id'];
6111
                                    break;
6112
                                } else {
6113
                                    $parent_id = $item['parent_item_id'];
6114
                                    if (empty($parent_arrays)) {
6115
                                        $parent_arrays[] = intval($item['id']);
6116
                                    }
6117
                                    $parent_arrays[] = $parent_id;
6118
                                    break;
6119
                                }
6120
                            }
6121
                        }
6122
                    }
6123
                }
6124
6125
                if (!empty($parent_arrays)) {
6126
                    $parent_arrays = array_reverse($parent_arrays);
6127
                    $val = '$elements';
6128
                    $x = 0;
6129
                    foreach ($parent_arrays as $item) {
6130
                        if ($x != count($parent_arrays) - 1) {
6131
                            $val .= '["'.$item.'"]["children"]';
6132
                        } else {
6133
                            $val .= '["'.$item.'"]["children"]';
6134
                        }
6135
                        $x++;
6136
                    }
6137
                    $val .= "";
6138
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6139
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6140
                } else {
6141
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6142
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6143
                }
6144
            }
6145
        }
6146
6147
        return [
6148
            'elements' => $elements,
6149
            'default_data' => $default_data,
6150
            'default_content' => $default_content,
6151
            'return_audio' => $return_audio,
6152
        ];
6153
    }
6154
6155
    /**
6156
     * @param string $updateAudio true/false strings
6157
     *
6158
     * @return string
6159
     */
6160
    public function returnLpItemList($updateAudio)
6161
    {
6162
        $result = $this->processBuildMenuElements($updateAudio);
6163
6164
        $html = self::print_recursive(
6165
            $result['elements'],
6166
            $result['default_data'],
6167
            $result['default_content']
6168
        );
6169
6170
        if (!empty($html)) {
6171
            $html .= Display::return_message(get_lang('Drag and drop an element here'));
6172
        }
6173
6174
        return $html;
6175
    }
6176
6177
    /**
6178
     * @param string $update_audio
6179
     * @param bool   $drop_element_here
6180
     *
6181
     * @return string
6182
     */
6183
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6184
    {
6185
        $result = $this->processBuildMenuElements($update_audio);
6186
6187
        $list = '<ul id="lp_item_list">';
6188
        $tree = $this->print_recursive(
6189
            $result['elements'],
6190
            $result['default_data'],
6191
            $result['default_content']
6192
        );
6193
6194
        if (!empty($tree)) {
6195
            $list .= $tree;
6196
        } else {
6197
            if ($drop_element_here) {
6198
                $list .= Display::return_message(get_lang('Drag and drop an element here'));
6199
            }
6200
        }
6201
        $list .= '</ul>';
6202
6203
        $return = Display::panelCollapse(
6204
            $this->name,
6205
            $list,
6206
            'scorm-list',
6207
            null,
6208
            'scorm-list-accordion',
6209
            'scorm-list-collapse'
6210
        );
6211
6212
        if ('true' === $update_audio) {
6213
            $return = $result['return_audio'];
6214
        }
6215
6216
        return $return;
6217
    }
6218
6219
    /**
6220
     * @param array $elements
6221
     * @param array $default_data
6222
     * @param array $default_content
6223
     *
6224
     * @return string
6225
     */
6226
    public function print_recursive($elements, $default_data, $default_content)
6227
    {
6228
        $return = '';
6229
        foreach ($elements as $key => $item) {
6230
            if (isset($item['load_data']) || empty($item['data'])) {
6231
                $item['data'] = $default_data[$item['load_data']];
6232
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6233
            }
6234
            $sub_list = '';
6235
            if (isset($item['type']) && 'dir' === $item['type']) {
6236
                // empty value
6237
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6238
            }
6239
            if (empty($item['children'])) {
6240
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6241
                $active = null;
6242
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6243
                    $active = 'active';
6244
                }
6245
                $return .= Display::tag(
6246
                    'li',
6247
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6248
                    ['id' => $key, 'class' => 'record li_container']
6249
                );
6250
            } else {
6251
                // Sections
6252
                $data = '';
6253
                if (isset($item['children'])) {
6254
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6255
                }
6256
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6257
                $return .= Display::tag(
6258
                    'li',
6259
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6260
                    ['id' => $key, 'class' => 'record li_container']
6261
                );
6262
            }
6263
        }
6264
6265
        return $return;
6266
    }
6267
6268
    /**
6269
     * This function builds the action menu.
6270
     *
6271
     * @param bool   $returnString           Optional
6272
     * @param bool   $showRequirementButtons Optional. Allow show the requirements button
6273
     * @param bool   $isConfigPage           Optional. If is the config page, show the edit button
6274
     * @param bool   $allowExpand            Optional. Allow show the expand/contract button
6275
     * @param string $action
6276
     * @param array  $extraField
6277
     *
6278
     * @return string
6279
     */
6280
    public function build_action_menu(
6281
        $returnString = false,
6282
        $showRequirementButtons = true,
6283
        $isConfigPage = false,
6284
        $allowExpand = true,
6285
        $action = '',
6286
        $extraField = []
6287
    ) {
6288
        $actionsRight = '';
6289
        $lpId = $this->lp_id;
6290
        if (!isset($extraField['backTo']) && empty($extraField['backTo'])) {
6291
            $back = Display::url(
6292
                Display::return_icon(
6293
                    'back.png',
6294
                    get_lang('Back to learning paths'),
6295
                    '',
6296
                    ICON_SIZE_MEDIUM
6297
                ),
6298
                'lp_controller.php?'.api_get_cidreq()
6299
            );
6300
        } else {
6301
            $back = Display::url(
6302
                Display::return_icon(
6303
                    'back.png',
6304
                    get_lang('Back'),
6305
                    '',
6306
                    ICON_SIZE_MEDIUM
6307
                ),
6308
                $extraField['backTo']
6309
            );
6310
        }
6311
6312
        /*if ($backToBuild) {
6313
            $back = Display::url(
6314
                Display::return_icon(
6315
                    'back.png',
6316
                    get_lang('GoBack'),
6317
                    '',
6318
                    ICON_SIZE_MEDIUM
6319
                ),
6320
                "lp_controller.php?action=add_item&type=step&lp_id=$lpId&".api_get_cidreq()
6321
            );
6322
        }*/
6323
6324
        $actionsLeft = $back;
6325
6326
        $actionsLeft .= Display::url(
6327
            Display::return_icon(
6328
                'preview_view.png',
6329
                get_lang('Preview'),
6330
                '',
6331
                ICON_SIZE_MEDIUM
6332
            ),
6333
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6334
                'action' => 'view',
6335
                'lp_id' => $lpId,
6336
                'isStudentView' => 'true',
6337
            ])
6338
        );
6339
6340
        $actionsLeft .= Display::url(
6341
            Display::return_icon(
6342
                'upload_audio.png',
6343
                get_lang('Add audio'),
6344
                '',
6345
                ICON_SIZE_MEDIUM
6346
            ),
6347
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6348
                'action' => 'admin_view',
6349
                'lp_id' => $lpId,
6350
                'updateaudio' => 'true',
6351
            ])
6352
        );
6353
6354
        $subscriptionSettings = self::getSubscriptionSettings();
6355
6356
        $request = api_request_uri();
6357
        if (false === strpos($request, 'edit')) {
6358
            $actionsLeft .= Display::url(
6359
                Display::return_icon(
6360
                    'settings.png',
6361
                    get_lang('Course settings'),
6362
                    '',
6363
                    ICON_SIZE_MEDIUM
6364
                ),
6365
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6366
                    'action' => 'edit',
6367
                    'lp_id' => $lpId,
6368
                ])
6369
            );
6370
        }
6371
6372
        if ((false === strpos($request, 'build') &&
6373
            false === strpos($request, 'add_item')) ||
6374
            in_array($action, ['add_audio'])
6375
        ) {
6376
            $actionsLeft .= Display::url(
6377
                Display::return_icon(
6378
                    'edit.png',
6379
                    get_lang('Edit'),
6380
                    '',
6381
                    ICON_SIZE_MEDIUM
6382
                ),
6383
                'lp_controller.php?'.http_build_query([
6384
                    'action' => 'build',
6385
                    'lp_id' => $lpId,
6386
                ]).'&'.api_get_cidreq()
6387
            );
6388
        }
6389
6390
        if (false === strpos(api_get_self(), 'lp_subscribe_users.php')) {
6391
            if (1 == $this->subscribeUsers &&
6392
                $subscriptionSettings['allow_add_users_to_lp']) {
6393
                $actionsLeft .= Display::url(
6394
                    Display::return_icon(
6395
                        'user.png',
6396
                        get_lang('Subscribe users to learning path'),
6397
                        '',
6398
                        ICON_SIZE_MEDIUM
6399
                    ),
6400
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=$lpId&".api_get_cidreq()
6401
                );
6402
            }
6403
        }
6404
6405
        if ($allowExpand) {
6406
            $actionsLeft .= Display::url(
6407
                Display::return_icon(
6408
                    'expand.png',
6409
                    get_lang('Expand'),
6410
                    ['id' => 'expand'],
6411
                    ICON_SIZE_MEDIUM
6412
                ).
6413
                Display::return_icon(
6414
                    'contract.png',
6415
                    get_lang('Collapse'),
6416
                    ['id' => 'contract', 'class' => 'hide'],
6417
                    ICON_SIZE_MEDIUM
6418
                ),
6419
                '#',
6420
                ['role' => 'button', 'id' => 'hide_bar_template']
6421
            );
6422
        }
6423
6424
        if ($showRequirementButtons) {
6425
            $buttons = [
6426
                [
6427
                    'title' => get_lang('Set previous step as prerequisite for each step'),
6428
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6429
                        'action' => 'set_previous_step_as_prerequisite',
6430
                        'lp_id' => $lpId,
6431
                    ]),
6432
                ],
6433
                [
6434
                    'title' => get_lang('Clear all prerequisites'),
6435
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6436
                        'action' => 'clear_prerequisites',
6437
                        'lp_id' => $lpId,
6438
                    ]),
6439
                ],
6440
            ];
6441
            $actionsRight = Display::groupButtonWithDropDown(
6442
                get_lang('Prerequisites options'),
6443
                $buttons,
6444
                true
6445
            );
6446
        }
6447
6448
        if (api_is_platform_admin() && isset($extraField['authorlp'])) {
6449
            $actionsLeft .= Display::url(
6450
                Display::return_icon(
6451
                    'add-groups.png',
6452
                    get_lang('Author'),
6453
                    '',
6454
                    ICON_SIZE_MEDIUM
6455
                ),
6456
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6457
                    'action' => 'author_view',
6458
                    'lp_id' => $lpId,
6459
                ])
6460
            );
6461
        }
6462
6463
        $toolbar = Display::toolbarAction(
6464
            'actions-lp-controller',
6465
            [$actionsLeft, $actionsRight]
6466
        );
6467
6468
        if ($returnString) {
6469
            return $toolbar;
6470
        }
6471
6472
        echo $toolbar;
6473
    }
6474
6475
    /**
6476
     * Creates the default learning path folder.
6477
     *
6478
     * @param array $course
6479
     * @param int   $creatorId
6480
     *
6481
     * @return bool
6482
     */
6483
    public static function generate_learning_path_folder($course, $creatorId = 0)
6484
    {
6485
        // Creating learning_path folder
6486
        $dir = 'learning_path';
6487
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6488
        $folder = false;
6489
6490
        $folderData = create_unexisting_directory(
6491
            $course,
6492
            $creatorId,
6493
            0,
6494
            null,
6495
            0,
6496
            '',
6497
            $dir,
6498
            get_lang('Learning paths'),
6499
            0
6500
        );
6501
6502
        if (!empty($folderData)) {
6503
            $folder = true;
6504
        }
6505
6506
        return $folder;
6507
    }
6508
6509
    /**
6510
     * @param array  $course
6511
     * @param string $lp_name
6512
     * @param int    $creatorId
6513
     *
6514
     * @return array
6515
     */
6516
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6517
    {
6518
        $filepath = '';
6519
        $dir = '/learning_path/';
6520
6521
        if (empty($lp_name)) {
6522
            $lp_name = $this->name;
6523
        }
6524
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6525
        $folder = self::generate_learning_path_folder($course, $creatorId);
6526
6527
        // Limits title size
6528
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6529
        $dir = $dir.$title;
6530
6531
        // Creating LP folder
6532
        $documentId = null;
6533
        if ($folder) {
6534
            $folderData = create_unexisting_directory(
6535
                $course,
6536
                $creatorId,
6537
                0,
6538
                0,
6539
                0,
6540
                $filepath,
6541
                $dir,
6542
                $lp_name
6543
            );
6544
            if (!empty($folderData)) {
6545
                $folder = true;
6546
            }
6547
6548
            $documentId = $folderData->getIid();
6549
            $dir = $dir.'/';
6550
            if ($folder) {
6551
                // $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6552
            }
6553
        }
6554
6555
        if (empty($documentId)) {
6556
            $dir = api_remove_trailing_slash($dir);
6557
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6558
        }
6559
6560
        $array = [
6561
            'dir' => $dir,
6562
            'filepath' => $filepath,
6563
            'folder' => $folder,
6564
            'id' => $documentId,
6565
        ];
6566
6567
        return $array;
6568
    }
6569
6570
    /**
6571
     * Create a new document //still needs some finetuning.
6572
     *
6573
     * @param array  $courseInfo
6574
     * @param string $content
6575
     * @param string $title
6576
     * @param string $extension
6577
     * @param int    $parentId
6578
     * @param int    $creatorId  creator id
6579
     *
6580
     * @return int
6581
     */
6582
    public function create_document(
6583
        $courseInfo,
6584
        $content = '',
6585
        $title = '',
6586
        $extension = 'html',
6587
        $parentId = 0,
6588
        $creatorId = 0
6589
    ) {
6590
        if (!empty($courseInfo)) {
6591
            $course_id = $courseInfo['real_id'];
6592
        } else {
6593
            $course_id = api_get_course_int_id();
6594
        }
6595
6596
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6597
        $sessionId = api_get_session_id();
6598
6599
        // Generates folder
6600
        $result = $this->generate_lp_folder($courseInfo);
6601
        $dir = $result['dir'];
6602
6603
        if (empty($parentId) || '/' == $parentId) {
6604
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6605
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6606
6607
            if ('/' === $parentId) {
6608
                $dir = '/';
6609
            }
6610
6611
            // Please, do not modify this dirname formatting.
6612
            if (strstr($dir, '..')) {
6613
                $dir = '/';
6614
            }
6615
6616
            if (!empty($dir[0]) && '.' == $dir[0]) {
6617
                $dir = substr($dir, 1);
6618
            }
6619
            if (!empty($dir[0]) && '/' != $dir[0]) {
6620
                $dir = '/'.$dir;
6621
            }
6622
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
6623
                $dir .= '/';
6624
            }
6625
        } else {
6626
            $parentInfo = DocumentManager::get_document_data_by_id(
6627
                $parentId,
6628
                $courseInfo['code']
6629
            );
6630
            if (!empty($parentInfo)) {
6631
                $dir = $parentInfo['path'].'/';
6632
            }
6633
        }
6634
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6635
        // is already escaped twice when it gets here.
6636
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6637
        if (!empty($title)) {
6638
            $title = api_replace_dangerous_char(stripslashes($title));
6639
        } else {
6640
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6641
        }
6642
6643
        $title = disable_dangerous_file($title);
6644
        $filename = $title;
6645
        $content = !empty($content) ? $content : $_POST['content_lp'];
6646
        $tmp_filename = $filename;
6647
        /*$i = 0;
6648
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
6649
            $tmp_filename = $filename.'_'.++$i;
6650
        }*/
6651
        $filename = $tmp_filename.'.'.$extension;
6652
6653
        if ('html' === $extension) {
6654
            $content = stripslashes($content);
6655
            $content = str_replace(
6656
                api_get_path(WEB_COURSE_PATH),
6657
                api_get_path(REL_PATH).'courses/',
6658
                $content
6659
            );
6660
6661
            // Change the path of mp3 to absolute.
6662
            // The first regexp deals with :// urls.
6663
            $content = preg_replace(
6664
                "|(flashvars=\"file=)([^:/]+)/|",
6665
                "$1".api_get_path(
6666
                    REL_COURSE_PATH
6667
                ).$courseInfo['path'].'/document/',
6668
                $content
6669
            );
6670
            // The second regexp deals with audio/ urls.
6671
            $content = preg_replace(
6672
                "|(flashvars=\"file=)([^/]+)/|",
6673
                "$1".api_get_path(
6674
                    REL_COURSE_PATH
6675
                ).$courseInfo['path'].'/document/$2/',
6676
                $content
6677
            );
6678
            // For flv player: To prevent edition problem with firefox,
6679
            // we have to use a strange tip (don't blame me please).
6680
            $content = str_replace(
6681
                '</body>',
6682
                '<style type="text/css">body{}</style></body>',
6683
                $content
6684
            );
6685
        }
6686
6687
        $save_file_path = $dir.$filename;
6688
6689
        $document = DocumentManager::addDocument(
6690
            $courseInfo,
6691
            $save_file_path,
6692
            'file',
6693
            '',
6694
            $tmp_filename,
6695
            '',
6696
            0, //readonly
6697
            true,
6698
            null,
6699
            $sessionId,
6700
            $creatorId,
6701
            false,
6702
            $content,
6703
            $parentId
6704
        );
6705
6706
        $document_id = $document->getIid();
6707
        if ($document_id) {
6708
            $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
6709
            $new_title = $originalTitle;
6710
6711
            if ($new_comment || $new_title) {
6712
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
6713
                $ct = '';
6714
                if ($new_comment) {
6715
                    $ct .= ", comment='".Database::escape_string($new_comment)."'";
6716
                }
6717
                if ($new_title) {
6718
                    $ct .= ", title='".Database::escape_string($new_title)."' ";
6719
                }
6720
6721
                $sql = "UPDATE $tbl_doc SET ".substr($ct, 1)."
6722
                        WHERE iid = $document_id ";
6723
                Database::query($sql);
6724
            }
6725
        }
6726
6727
        return $document_id;
6728
    }
6729
6730
    /**
6731
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
6732
     */
6733
    public function edit_document()
6734
    {
6735
        $repo = Container::getDocumentRepository();
6736
        if (isset($_REQUEST['document_id']) && !empty($_REQUEST['document_id'])) {
6737
            $id = (int) $_REQUEST['document_id'];
6738
            /** @var CDocument $document */
6739
            $document = $repo->find($id);
6740
6741
            if ($document->getResourceNode()->hasEditableTextContent()) {
6742
                $repo->updateResourceFileContent($document, $_REQUEST['content_lp']);
6743
            }
6744
6745
            $document->setTitle($_REQUEST['title']);
6746
            $repo->update($document);
6747
        }
6748
    }
6749
6750
    /**
6751
     * Displays the selected item, with a panel for manipulating the item.
6752
     *
6753
     * @param CLpItem $lpItem
6754
     * @param string  $msg
6755
     * @param bool    $show_actions
6756
     *
6757
     * @return string
6758
     */
6759
    public function display_item($lpItem, $msg = null, $show_actions = true)
6760
    {
6761
        $course_id = api_get_course_int_id();
6762
        $return = '';
6763
6764
        if (empty($lpItem)) {
6765
            return '';
6766
        }
6767
        $item_id = $lpItem->getIid();
6768
        $itemType = $lpItem->getItemType();
6769
        $lpId = $lpItem->getLp()->getIid();
6770
        $path = $lpItem->getPath();
6771
6772
        Session::write('parent_item_id', 'dir' === $itemType ? $item_id : 0);
6773
6774
        // Prevents wrong parent selection for document, see Bug#1251.
6775
        if ('dir' !== $itemType) {
6776
            Session::write('parent_item_id', $lpItem->getParentItemId());
6777
        }
6778
6779
        if ($show_actions) {
6780
            $return .= $this->displayItemMenu($lpItem);
6781
        }
6782
        $return .= '<div style="padding:10px;">';
6783
6784
        if ('' != $msg) {
6785
            $return .= $msg;
6786
        }
6787
6788
        $return .= '<h3>'.$lpItem->getTitle().'</h3>';
6789
6790
        switch ($itemType) {
6791
            case TOOL_THREAD:
6792
                $link = $this->rl_get_resource_link_for_learnpath(
6793
                    $course_id,
6794
                    $lpId,
6795
                    $item_id,
6796
                    0
6797
                );
6798
                $return .= Display::url(
6799
                    get_lang('Go to thread'),
6800
                    $link,
6801
                    ['class' => 'btn btn-primary']
6802
                );
6803
                break;
6804
            case TOOL_FORUM:
6805
                $return .= Display::url(
6806
                    get_lang('Go to the forum'),
6807
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$path,
6808
                    ['class' => 'btn btn-primary']
6809
                );
6810
                break;
6811
            case TOOL_QUIZ:
6812
                if (!empty($path)) {
6813
                    $exercise = new Exercise();
6814
                    $exercise->read($path);
6815
                    $return .= $exercise->description.'<br />';
6816
                    $return .= Display::url(
6817
                        get_lang('Go to exercise'),
6818
                        api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
6819
                        ['class' => 'btn btn-primary']
6820
                    );
6821
                }
6822
                break;
6823
            case TOOL_LP_FINAL_ITEM:
6824
                $return .= $this->getSavedFinalItem();
6825
                break;
6826
            case TOOL_DOCUMENT:
6827
            case TOOL_READOUT_TEXT:
6828
                $repo = Container::getDocumentRepository();
6829
                /** @var CDocument $document */
6830
                $document = $repo->find($lpItem->getPath());
6831
                $return .= $this->display_document($document, true, true);
6832
                break;
6833
            case TOOL_HOTPOTATOES:
6834
                $return .= $this->display_document($document, false, true);
6835
                break;
6836
        }
6837
        $return .= '</div>';
6838
6839
        return $return;
6840
    }
6841
6842
    /**
6843
     * Shows the needed forms for editing a specific item.
6844
     *
6845
     * @param CLpItem $lpItem
6846
     *
6847
     * @throws Exception
6848
     * @throws HTML_QuickForm_Error
6849
     *
6850
     * @return string
6851
     */
6852
    public function display_edit_item($lpItem, $excludeExtraFields = [])
6853
    {
6854
        $course_id = api_get_course_int_id();
6855
        $return = '';
6856
6857
        if (empty($lpItem)) {
6858
            return '';
6859
        }
6860
        $item_id = $lpItem->getIid();
6861
        $itemType = $lpItem->getItemType();
6862
        $path = $lpItem->getPath();
6863
6864
        switch ($itemType) {
6865
            case 'dir':
6866
            case 'asset':
6867
            case 'sco':
6868
                if (isset($_GET['view']) && 'build' === $_GET['view']) {
6869
                    $return .= $this->displayItemMenu($lpItem);
6870
                    $return .= $this->display_item_form(
6871
                        $lpItem,
6872
                        'edit'
6873
                    );
6874
                } else {
6875
                    $return .= $this->display_item_form(
6876
                        $lpItem,
6877
                        'edit_item'
6878
                    );
6879
                }
6880
                break;
6881
            case TOOL_LP_FINAL_ITEM:
6882
            case TOOL_DOCUMENT:
6883
            case TOOL_READOUT_TEXT:
6884
                $return .= $this->displayItemMenu($lpItem);
6885
                $return .= $this->displayDocumentForm('edit', $lpItem);
6886
                break;
6887
            case TOOL_LINK:
6888
                $link = null;
6889
                if (!empty($path)) {
6890
                    $repo = Container::getLinkRepository();
6891
                    $link = $repo->find($path);
6892
                }
6893
                $return .= $this->displayItemMenu($lpItem);
6894
                $return .= $this->display_link_form('edit', $lpItem, $link);
6895
6896
                break;
6897
            case TOOL_QUIZ:
6898
                if (!empty($path)) {
6899
                    $repo = Container::getQuizRepository();
6900
                    $resource = $repo->find($path);
6901
                }
6902
                $return .= $this->displayItemMenu($lpItem);
6903
                $return .= $this->display_quiz_form('edit', $lpItem, $resource);
6904
                break;
6905
            /*case TOOL_HOTPOTATOES:
6906
                $return .= $this->displayItemMenu($lpItem);
6907
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
6908
                break;*/
6909
            case TOOL_STUDENTPUBLICATION:
6910
                if (!empty($path)) {
6911
                    $repo = Container::getStudentPublicationRepository();
6912
                    $resource = $repo->find($path);
6913
                }
6914
                $return .= $this->displayItemMenu($lpItem);
6915
                $return .= $this->display_student_publication_form('edit', $lpItem, $resource);
6916
                break;
6917
            case TOOL_FORUM:
6918
                if (!empty($path)) {
6919
                    $repo = Container::getForumRepository();
6920
                    $resource = $repo->find($path);
6921
                }
6922
                $return .= $this->displayItemMenu($lpItem);
6923
                $return .= $this->display_forum_form('edit', $lpItem, $resource);
6924
                break;
6925
            case TOOL_THREAD:
6926
                if (!empty($path)) {
6927
                    $repo = Container::getForumPostRepository();
6928
                    $resource = $repo->find($path);
6929
                }
6930
                $return .= $this->displayItemMenu($lpItem);
6931
                $return .= $this->display_thread_form('edit', $lpItem, $resource);
6932
                break;
6933
        }
6934
6935
        return $return;
6936
    }
6937
6938
    /**
6939
     * Function that displays a list with al the resources that
6940
     * could be added to the learning path.
6941
     *
6942
     * @throws Exception
6943
     * @throws HTML_QuickForm_Error
6944
     *
6945
     * @return bool
6946
     */
6947
    public function displayResources()
6948
    {
6949
        // Get all the docs.
6950
        $documents = $this->get_documents(true);
6951
6952
        // Get all the exercises.
6953
        $exercises = $this->get_exercises();
6954
6955
        // Get all the links.
6956
        $links = $this->get_links();
6957
6958
        // Get all the student publications.
6959
        $works = $this->get_student_publications();
6960
6961
        // Get all the forums.
6962
        $forums = $this->get_forums();
6963
6964
        // Get the final item form (see BT#11048) .
6965
        $finish = $this->getFinalItemForm();
6966
6967
        $headers = [
6968
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
6969
            Display::return_icon('quiz.png', get_lang('Tests'), [], ICON_SIZE_BIG),
6970
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
6971
            Display::return_icon('works.png', get_lang('Assignments'), [], ICON_SIZE_BIG),
6972
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
6973
            Display::return_icon('add_learnpath_section.png', get_lang('Add section'), [], ICON_SIZE_BIG),
6974
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
6975
        ];
6976
6977
        echo Display::return_message(
6978
            get_lang('Click on the [Learner view] button to see your learning path'),
6979
            'normal'
6980
        );
6981
        $section = $this->displayNewSectionForm();
6982
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
6983
6984
        echo Display::tabs(
6985
            $headers,
6986
            [
6987
                $documents,
6988
                $exercises,
6989
                $links,
6990
                $works,
6991
                $forums,
6992
                $section,
6993
                $finish,
6994
            ],
6995
            'resource_tab',
6996
            [],
6997
            [],
6998
            $selected
6999
        );
7000
7001
        return true;
7002
    }
7003
7004
    /**
7005
     * Returns the extension of a document.
7006
     *
7007
     * @param string $filename
7008
     *
7009
     * @return string Extension (part after the last dot)
7010
     */
7011
    public function get_extension($filename)
7012
    {
7013
        $explode = explode('.', $filename);
7014
7015
        return $explode[count($explode) - 1];
7016
    }
7017
7018
    /**
7019
     * @return string
7020
     */
7021
    public function getCurrentBuildingModeURL()
7022
    {
7023
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
7024
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
7025
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
7026
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
7027
7028
        $currentUrl = api_get_self().'?'.api_get_cidreq().
7029
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
7030
7031
        return $currentUrl;
7032
    }
7033
7034
    /**
7035
     * Displays a document by id.
7036
     *
7037
     * @param CDocument $document
7038
     * @param bool      $show_title
7039
     * @param bool      $iframe
7040
     * @param bool      $edit_link
7041
     *
7042
     * @return string
7043
     */
7044
    public function display_document($document, $show_title = false, $iframe = true, $edit_link = false)
7045
    {
7046
        $return = '';
7047
        if (!$document) {
7048
            return '';
7049
        }
7050
7051
        $repo = Container::getDocumentRepository();
7052
7053
        // TODO: Add a path filter.
7054
        if ($iframe) {
7055
            //$url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.str_replace('%2F', '/', urlencode($row_doc['path'])).'?'.api_get_cidreq();
7056
            $url = $repo->getResourceFileUrl($document);
7057
7058
            $return .= '<iframe
7059
                id="learnpath_preview_frame"
7060
                frameborder="0"
7061
                height="400"
7062
                width="100%"
7063
                scrolling="auto"
7064
                src="'.$url.'"></iframe>';
7065
        } else {
7066
            $return = $repo->getResourceFileContent($document);
7067
        }
7068
7069
        return $return;
7070
    }
7071
7072
    /**
7073
     * Return HTML form to add/edit a link item.
7074
     *
7075
     * @param string  $action (add/edit)
7076
     * @param CLpItem $lpItem
7077
     * @param CLink   $link
7078
     *
7079
     * @throws Exception
7080
     * @throws HTML_QuickForm_Error
7081
     *
7082
     * @return string HTML form
7083
     */
7084
    public function display_link_form($action = 'add', $lpItem, $link)
7085
    {
7086
        $item_url = '';
7087
        if ($link) {
7088
            $item_url = stripslashes($link->getUrl());
7089
        }
7090
        $form = new FormValidator(
7091
            'edit_link',
7092
            'POST',
7093
            $this->getCurrentBuildingModeURL()
7094
        );
7095
7096
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7097
7098
        $urlAttributes = ['class' => 'learnpath_item_form'];
7099
        $urlAttributes['disabled'] = 'disabled';
7100
        $form->addElement('url', 'url', get_lang('URL'), $urlAttributes);
7101
        $form->setDefault('url', $item_url);
7102
7103
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7104
7105
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7106
    }
7107
7108
    /**
7109
     * Return HTML form to add/edit a quiz.
7110
     *
7111
     * @param string  $action   Action (add/edit)
7112
     * @param CLpItem $lpItem   Item ID if already exists
7113
     * @param CQuiz   $exercise Extra information (quiz ID if integer)
7114
     *
7115
     * @throws Exception
7116
     *
7117
     * @return string HTML form
7118
     */
7119
    public function display_quiz_form($action = 'add', $lpItem, $exercise)
7120
    {
7121
        $form = new FormValidator(
7122
            'quiz_form',
7123
            'POST',
7124
            $this->getCurrentBuildingModeURL()
7125
        );
7126
7127
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7128
7129
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7130
7131
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7132
    }
7133
7134
    /**
7135
     * Return the form to display the forum edit/add option.
7136
     *
7137
     * @param CLpItem $lpItem
7138
     *
7139
     * @throws Exception
7140
     *
7141
     * @return string HTML form
7142
     */
7143
    public function display_forum_form($action = 'add', $lpItem, $resource)
7144
    {
7145
        $form = new FormValidator(
7146
            'forum_form',
7147
            'POST',
7148
            $this->getCurrentBuildingModeURL()
7149
        );
7150
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7151
7152
        if ('add' === $action) {
7153
            $form->addButtonSave(get_lang('Add forum to course'), 'submit_button');
7154
        } else {
7155
            $form->addButtonSave(get_lang('Edit the current forum'), 'submit_button');
7156
        }
7157
7158
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7159
    }
7160
7161
    /**
7162
     * Return HTML form to add/edit forum threads.
7163
     *
7164
     * @param string  $action
7165
     * @param CLpItem $lpItem
7166
     * @param string  $resource
7167
     *
7168
     * @throws Exception
7169
     *
7170
     * @return string HTML form
7171
     */
7172
    public function display_thread_form($action = 'add', $lpItem, $resource)
7173
    {
7174
        $form = new FormValidator(
7175
            'thread_form',
7176
            'POST',
7177
            $this->getCurrentBuildingModeURL()
7178
        );
7179
7180
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7181
7182
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7183
7184
        return $form->returnForm();
7185
    }
7186
7187
    /**
7188
     * Return the HTML form to display an item (generally a dir item).
7189
     *
7190
     * @param CLpItem $lpItem
7191
     * @param string  $action
7192
     *
7193
     * @throws Exception
7194
     * @throws HTML_QuickForm_Error
7195
     *
7196
     * @return string HTML form
7197
     */
7198
    public function display_item_form(
7199
        $lpItem,
7200
        $action = 'add_item'
7201
    ) {
7202
        $item_type = $lpItem->getItemType();
7203
7204
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
7205
7206
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
7207
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7208
7209
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
7210
7211
        return $form->returnForm();
7212
    }
7213
7214
    /**
7215
     * Return HTML form to add/edit a student publication (work).
7216
     *
7217
     * @param string              $action
7218
     * @param CStudentPublication $resource
7219
     *
7220
     * @throws Exception
7221
     *
7222
     * @return string HTML form
7223
     */
7224
    public function display_student_publication_form(
7225
        $action = 'add',
7226
        CLpItem $lpItem,
7227
        $resource
7228
    ) {
7229
        $form = new FormValidator('frm_student_publication', 'post', '#');
7230
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7231
7232
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7233
7234
        $return = '<div class="sectioncomment">';
7235
        $return .= $form->returnForm();
7236
        $return .= '</div>';
7237
7238
        return $return;
7239
    }
7240
7241
    public function displayNewSectionForm()
7242
    {
7243
        $action = 'add_item';
7244
        $item_type = 'dir';
7245
7246
        $lpItem = new CLpItem();
7247
        $lpItem->setItemType('dir');
7248
7249
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
7250
7251
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
7252
        LearnPathItemForm::setForm($form, 'add', $this, $lpItem);
7253
7254
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
7255
        $form->addElement('hidden', 'type', 'dir');
7256
7257
        return $form->returnForm();
7258
    }
7259
7260
    /**
7261
     * Returns the form to update or create a document.
7262
     *
7263
     * @param string  $action (add/edit)
7264
     * @param CLpItem $lpItem
7265
     *
7266
     * @throws HTML_QuickForm_Error
7267
     * @throws Exception
7268
     *
7269
     * @return string HTML form
7270
     */
7271
    public function displayDocumentForm($action = 'add', $lpItem = null)
7272
    {
7273
        if (empty($lpItem)) {
7274
            return '';
7275
        }
7276
7277
        $courseInfo = api_get_course_info();
7278
7279
        $form = new FormValidator(
7280
            'form',
7281
            'POST',
7282
            $this->getCurrentBuildingModeURL(),
7283
            '',
7284
            ['enctype' => 'multipart/form-data']
7285
        );
7286
7287
        $data = $this->generate_lp_folder($courseInfo);
7288
7289
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7290
7291
        switch ($action) {
7292
            case 'add':
7293
                $folders = DocumentManager::get_all_document_folders(
7294
                    $courseInfo,
7295
                    0,
7296
                    true
7297
                );
7298
                DocumentManager::build_directory_selector(
7299
                    $folders,
7300
                    '',
7301
                    [],
7302
                    true,
7303
                    $form,
7304
                    'directory_parent_id'
7305
                );
7306
7307
                if (isset($data['id'])) {
7308
                    $defaults['directory_parent_id'] = $data['id'];
7309
                }
7310
7311
                break;
7312
        }
7313
7314
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7315
7316
        return $form->returnForm();
7317
    }
7318
7319
    /**
7320
     * @param array  $courseInfo
7321
     * @param string $content
7322
     * @param string $title
7323
     * @param int    $parentId
7324
     *
7325
     * @throws \Doctrine\ORM\ORMException
7326
     * @throws \Doctrine\ORM\OptimisticLockException
7327
     * @throws \Doctrine\ORM\TransactionRequiredException
7328
     *
7329
     * @return int
7330
     */
7331
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
7332
    {
7333
        $creatorId = api_get_user_id();
7334
        $sessionId = api_get_session_id();
7335
7336
        // Generates folder
7337
        $result = $this->generate_lp_folder($courseInfo);
7338
        $dir = $result['dir'];
7339
7340
        if (empty($parentId) || '/' == $parentId) {
7341
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
7342
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
7343
7344
            if ('/' === $parentId) {
7345
                $dir = '/';
7346
            }
7347
7348
            // Please, do not modify this dirname formatting.
7349
            if (strstr($dir, '..')) {
7350
                $dir = '/';
7351
            }
7352
7353
            if (!empty($dir[0]) && '.' == $dir[0]) {
7354
                $dir = substr($dir, 1);
7355
            }
7356
            if (!empty($dir[0]) && '/' != $dir[0]) {
7357
                $dir = '/'.$dir;
7358
            }
7359
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
7360
                $dir .= '/';
7361
            }
7362
        } else {
7363
            $parentInfo = DocumentManager::get_document_data_by_id(
7364
                $parentId,
7365
                $courseInfo['code']
7366
            );
7367
            if (!empty($parentInfo)) {
7368
                $dir = $parentInfo['path'].'/';
7369
            }
7370
        }
7371
7372
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7373
7374
        if (!is_dir($filepath)) {
7375
            $dir = '/';
7376
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7377
        }
7378
7379
        $originalTitle = !empty($title) ? $title : $_POST['title'];
7380
7381
        if (!empty($title)) {
7382
            $title = api_replace_dangerous_char(stripslashes($title));
7383
        } else {
7384
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
7385
        }
7386
7387
        $title = disable_dangerous_file($title);
7388
        $filename = $title;
7389
        $content = !empty($content) ? $content : $_POST['content_lp'];
7390
        $tmpFileName = $filename;
7391
7392
        $i = 0;
7393
        while (file_exists($filepath.$tmpFileName.'.html')) {
7394
            $tmpFileName = $filename.'_'.++$i;
7395
        }
7396
7397
        $filename = $tmpFileName.'.html';
7398
        $content = stripslashes($content);
7399
7400
        if (file_exists($filepath.$filename)) {
7401
            return 0;
7402
        }
7403
7404
        $putContent = file_put_contents($filepath.$filename, $content);
7405
7406
        if (false === $putContent) {
7407
            return 0;
7408
        }
7409
7410
        $fileSize = filesize($filepath.$filename);
7411
        $saveFilePath = $dir.$filename;
7412
7413
        $document = DocumentManager::addDocument(
7414
            $courseInfo,
7415
            $saveFilePath,
7416
            'file',
7417
            $fileSize,
7418
            $tmpFileName,
7419
            '',
7420
            0, //readonly
7421
            true,
7422
            null,
7423
            $sessionId,
7424
            $creatorId
7425
        );
7426
7427
        $documentId = $document->getId();
7428
7429
        if (!$document) {
7430
            return 0;
7431
        }
7432
7433
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7434
        $newTitle = $originalTitle;
7435
7436
        if ($newComment || $newTitle) {
7437
            $em = Database::getManager();
7438
7439
            if ($newComment) {
7440
                $document->setComment($newComment);
7441
            }
7442
7443
            if ($newTitle) {
7444
                $document->setTitle($newTitle);
7445
            }
7446
7447
            $em->persist($document);
7448
            $em->flush();
7449
        }
7450
7451
        return $documentId;
7452
    }
7453
7454
    /**
7455
     * Displays the menu for manipulating a step.
7456
     *
7457
     * @return string
7458
     */
7459
    public function displayItemMenu(CLpItem $lpItem)
7460
    {
7461
        $item_id = $lpItem->getIid();
7462
        $audio = $lpItem->getAudio();
7463
        $itemType = $lpItem->getItemType();
7464
        $path = $lpItem->getPath();
7465
7466
        $return = '<div class="actions">';
7467
        $audio_player = null;
7468
        // We display an audio player if needed.
7469
        if (!empty($audio)) {
7470
            /*$webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
7471
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
7472
                .'<audio src="'.$webAudioPath.'" controls>'
7473
                .'</div><br>';*/
7474
        }
7475
7476
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
7477
7478
        if (TOOL_LP_FINAL_ITEM !== $itemType) {
7479
            $return .= Display::url(
7480
                Display::return_icon(
7481
                    'edit.png',
7482
                    get_lang('Edit'),
7483
                    [],
7484
                    ICON_SIZE_SMALL
7485
                ),
7486
                $url.'&action=edit_item&path_item='.$path
7487
            );
7488
7489
            /*$return .= Display::url(
7490
                Display::return_icon(
7491
                    'move.png',
7492
                    get_lang('Move'),
7493
                    [],
7494
                    ICON_SIZE_SMALL
7495
                ),
7496
                $url.'&action=move_item'
7497
            );*/
7498
        }
7499
7500
        // Commented for now as prerequisites cannot be added to chapters.
7501
        if ('dir' !== $itemType) {
7502
            $return .= Display::url(
7503
                Display::return_icon(
7504
                    'accept.png',
7505
                    get_lang('Prerequisites'),
7506
                    [],
7507
                    ICON_SIZE_SMALL
7508
                ),
7509
                $url.'&action=edit_item_prereq'
7510
            );
7511
        }
7512
        $return .= Display::url(
7513
            Display::return_icon(
7514
                'delete.png',
7515
                get_lang('Delete'),
7516
                [],
7517
                ICON_SIZE_SMALL
7518
            ),
7519
            $url.'&action=delete_item'
7520
        );
7521
7522
        /*if (in_array($itemType, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
7523
            $documentData = DocumentManager::get_document_data_by_id($path, $course_code);
7524
            if (empty($documentData)) {
7525
                // Try with iid
7526
                $table = Database::get_course_table(TABLE_DOCUMENT);
7527
                $sql = "SELECT path FROM $table
7528
                        WHERE
7529
                              c_id = ".api_get_course_int_id()." AND
7530
                              iid = ".$path." AND
7531
                              path NOT LIKE '%_DELETED_%'";
7532
                $result = Database::query($sql);
7533
                $documentData = Database::fetch_array($result);
7534
                if ($documentData) {
7535
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
7536
                }
7537
            }
7538
            if (isset($documentData['absolute_path_from_document'])) {
7539
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
7540
            }
7541
        }*/
7542
7543
        $return .= '</div>';
7544
7545
        if (!empty($audio_player)) {
7546
            $return .= $audio_player;
7547
        }
7548
7549
        return $return;
7550
    }
7551
7552
    /**
7553
     * Creates the javascript needed for filling up the checkboxes without page reload.
7554
     *
7555
     * @return string
7556
     */
7557
    public function get_js_dropdown_array()
7558
    {
7559
        $course_id = api_get_course_int_id();
7560
        $return = 'var child_name = new Array();'."\n";
7561
        $return .= 'var child_value = new Array();'."\n\n";
7562
        $return .= 'child_name[0] = new Array();'."\n";
7563
        $return .= 'child_value[0] = new Array();'."\n\n";
7564
7565
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7566
        $sql = "SELECT * FROM ".$tbl_lp_item."
7567
                WHERE
7568
                    c_id = $course_id AND
7569
                    lp_id = ".$this->lp_id." AND
7570
                    parent_item_id = 0
7571
                ORDER BY display_order ASC";
7572
        Database::query($sql);
7573
        $i = 0;
7574
7575
        $list = $this->getItemsForForm(true);
7576
7577
        foreach ($list as $row_zero) {
7578
            if (TOOL_LP_FINAL_ITEM !== $row_zero['item_type']) {
7579
                if (TOOL_QUIZ == $row_zero['item_type']) {
7580
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
7581
                }
7582
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
7583
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
7584
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
7585
            }
7586
        }
7587
7588
        $return .= "\n";
7589
        $sql = "SELECT * FROM $tbl_lp_item
7590
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7591
        $res = Database::query($sql);
7592
        while ($row = Database::fetch_array($res)) {
7593
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
7594
                           WHERE
7595
                                c_id = ".$course_id." AND
7596
                                parent_item_id = ".$row['iid']."
7597
                           ORDER BY display_order ASC";
7598
            $res_parent = Database::query($sql_parent);
7599
            $i = 0;
7600
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
7601
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
7602
7603
            while ($row_parent = Database::fetch_array($res_parent)) {
7604
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
7605
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
7606
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
7607
            }
7608
            $return .= "\n";
7609
        }
7610
7611
        $return .= "
7612
            function load_cbo(id) {
7613
                if (!id) {
7614
                    return false;
7615
                }
7616
7617
                var cbo = document.getElementById('previous');
7618
                for(var i = cbo.length - 1; i > 0; i--) {
7619
                    cbo.options[i] = null;
7620
                }
7621
7622
                var k=0;
7623
                for(var i = 1; i <= child_name[id].length; i++){
7624
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
7625
                    option.style.paddingLeft = '40px';
7626
                    cbo.options[i] = option;
7627
                    k = i;
7628
                }
7629
7630
                cbo.options[k].selected = true;
7631
                //$('#previous').selectpicker('refresh');
7632
            }";
7633
7634
        return $return;
7635
    }
7636
7637
    /**
7638
     * Display the form to allow moving an item.
7639
     *
7640
     * @param CLpItem $lpItem
7641
     *
7642
     * @throws Exception
7643
     * @throws HTML_QuickForm_Error
7644
     *
7645
     * @return string HTML form
7646
     */
7647
    public function display_move_item($lpItem)
7648
    {
7649
        $return = '';
7650
        $path = $lpItem->getPath();
7651
7652
        if ($lpItem) {
7653
            $itemType = $lpItem->getItemType();
7654
            switch ($itemType) {
7655
                case 'dir':
7656
                case 'asset':
7657
                    $return .= $this->displayItemMenu($lpItem);
7658
                    $return .= $this->display_item_form(
7659
                        $lpItem,
7660
                        get_lang('Move the current section'),
7661
                        'move',
7662
                        $row
7663
                    );
7664
                    break;
7665
                case TOOL_DOCUMENT:
7666
                    $return .= $this->displayItemMenu($lpItem);
7667
                    $return .= $this->displayDocumentForm('move', $lpItem);
7668
                    break;
7669
                case TOOL_LINK:
7670
                    $link = null;
7671
                    if (!empty($path)) {
7672
                        $repo = Container::getLinkRepository();
7673
                        $link = $repo->find($path);
7674
                    }
7675
                    $return .= $this->displayItemMenu($lpItem);
7676
                    $return .= $this->display_link_form('move', $lpItem, $link);
7677
                    break;
7678
                case TOOL_HOTPOTATOES:
7679
                    $return .= $this->displayItemMenu($lpItem);
7680
                    $return .= $this->display_link_form('move', $lpItem, $row);
7681
                    break;
7682
                case TOOL_QUIZ:
7683
                    $return .= $this->displayItemMenu($lpItem);
7684
                    $return .= $this->display_quiz_form('move', $lpItem, $row);
7685
                    break;
7686
                case TOOL_STUDENTPUBLICATION:
7687
                    $return .= $this->displayItemMenu($lpItem);
7688
                    $return .= $this->display_student_publication_form('move', $lpItem, $row);
7689
                    break;
7690
                case TOOL_FORUM:
7691
                    $return .= $this->displayItemMenu($lpItem);
7692
                    $return .= $this->display_forum_form('move', $lpItem, $row);
7693
                    break;
7694
                case TOOL_THREAD:
7695
                    $return .= $this->displayItemMenu($lpItem);
7696
                    $return .= $this->display_forum_form('move', $lpItem, $row);
7697
                    break;
7698
            }
7699
        }
7700
7701
        return $return;
7702
    }
7703
7704
    /**
7705
     * Return HTML form to allow prerequisites selection.
7706
     *
7707
     * @todo use FormValidator
7708
     *
7709
     * @return string HTML form
7710
     */
7711
    public function display_item_prerequisites_form(CLpItem $lpItem)
7712
    {
7713
        $course_id = api_get_course_int_id();
7714
        $prerequisiteId = $lpItem->getPrerequisite();
7715
        $itemId = $lpItem->getIid();
7716
7717
        $return = '<legend>';
7718
        $return .= get_lang('Add/edit prerequisites');
7719
        $return .= '</legend>';
7720
        $return .= '<form method="POST">';
7721
        $return .= '<div class="table-responsive">';
7722
        $return .= '<table class="table table-hover">';
7723
        $return .= '<thead>';
7724
        $return .= '<tr>';
7725
        $return .= '<th>'.get_lang('Prerequisites').'</th>';
7726
        $return .= '<th width="140">'.get_lang('minimum').'</th>';
7727
        $return .= '<th width="140">'.get_lang('maximum').'</th>';
7728
        $return .= '</tr>';
7729
        $return .= '</thead>';
7730
7731
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
7732
        $return .= '<tbody>';
7733
        $return .= '<tr>';
7734
        $return .= '<td colspan="3">';
7735
        $return .= '<div class="radio learnpath"><label for="idnone">';
7736
        $return .= '<input checked="checked" id="idnone" name="prerequisites" type="radio" />';
7737
        $return .= get_lang('none').'</label>';
7738
        $return .= '</div>';
7739
        $return .= '</tr>';
7740
7741
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7742
        $sql = "SELECT * FROM $tbl_lp_item
7743
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7744
        $result = Database::query($sql);
7745
7746
        $selectedMinScore = [];
7747
        $selectedMaxScore = [];
7748
        $masteryScore = [];
7749
        while ($row = Database::fetch_array($result)) {
7750
            if ($row['iid'] == $itemId) {
7751
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
7752
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
7753
            }
7754
            $masteryScore[$row['iid']] = $row['mastery_score'];
7755
        }
7756
7757
        $arrLP = $this->getItemsForForm();
7758
        $this->tree_array($arrLP);
7759
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7760
        unset($this->arrMenu);
7761
7762
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

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

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

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
7763
            $item = $arrLP[$i];
7764
7765
            if ($item['id'] == $itemId) {
7766
                break;
7767
            }
7768
7769
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
7770
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
7771
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
7772
7773
            $return .= '<tr>';
7774
            $return .= '<td '.((TOOL_QUIZ != $item['item_type'] && TOOL_HOTPOTATOES != $item['item_type']) ? ' colspan="3"' : '').'>';
7775
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
7776
            $return .= '<label for="id'.$item['id'].'">';
7777
7778
            $checked = '';
7779
            if (null !== $prerequisiteId) {
7780
                $checked = in_array($prerequisiteId, [$item['id'], $item['ref']]) ? ' checked="checked" ' : '';
7781
            }
7782
7783
            $disabled = 'dir' === $item['item_type'] ? ' disabled="disabled" ' : '';
7784
7785
            $return .= '<input '.$checked.' '.$disabled.' id="id'.$item['id'].'" name="prerequisites" type="radio" value="'.$item['id'].'" />';
7786
7787
            $icon_name = str_replace(' ', '', $item['item_type']);
7788
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
7789
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
7790
            } else {
7791
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
7792
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
7793
                } else {
7794
                    $return .= Display::return_icon('folder_document.png');
7795
                }
7796
            }
7797
7798
            $return .= $item['title'].'</label>';
7799
            $return .= '</div>';
7800
            $return .= '</td>';
7801
7802
            if (TOOL_QUIZ == $item['item_type']) {
7803
                // lets update max_score Tests information depending of the Tests Advanced properties
7804
                $lpItemObj = new LpItem($course_id, $item['id']);
7805
                $exercise = new Exercise($course_id);
7806
                $exercise->read($lpItemObj->path);
7807
                $lpItemObj->max_score = $exercise->get_max_score();
7808
                $lpItemObj->update();
7809
                $item['max_score'] = $lpItemObj->max_score;
7810
7811
                if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
7812
                    // Backwards compatibility with 1.9.x use mastery_score as min value
7813
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
7814
                }
7815
7816
                $return .= '<td>';
7817
                $return .= '<input
7818
                    class="form-control"
7819
                    size="4" maxlength="3"
7820
                    name="min_'.$item['id'].'"
7821
                    type="number"
7822
                    min="0"
7823
                    step="any"
7824
                    max="'.$item['max_score'].'"
7825
                    value="'.$selectedMinScoreValue.'"
7826
                />';
7827
                $return .= '</td>';
7828
                $return .= '<td>';
7829
                $return .= '<input
7830
                    class="form-control"
7831
                    size="4"
7832
                    maxlength="3"
7833
                    name="max_'.$item['id'].'"
7834
                    type="number"
7835
                    min="0"
7836
                    step="any"
7837
                    max="'.$item['max_score'].'"
7838
                    value="'.$selectedMaxScoreValue.'"
7839
                />';
7840
                $return .= '</td>';
7841
            }
7842
7843
            if (TOOL_HOTPOTATOES == $item['item_type']) {
7844
                $return .= '<td>';
7845
                $return .= '<input
7846
                    size="4"
7847
                    maxlength="3"
7848
                    name="min_'.$item['id'].'"
7849
                    type="number"
7850
                    min="0"
7851
                    step="any"
7852
                    max="'.$item['max_score'].'"
7853
                    value="'.$selectedMinScoreValue.'"
7854
                />';
7855
                $return .= '</td>';
7856
                $return .= '<td>';
7857
                $return .= '<input
7858
                    size="4"
7859
                    maxlength="3"
7860
                    name="max_'.$item['id'].'"
7861
                    type="number"
7862
                    min="0"
7863
                    step="any"
7864
                    max="'.$item['max_score'].'"
7865
                    value="'.$selectedMaxScoreValue.'"
7866
                />';
7867
                $return .= '</td>';
7868
            }
7869
            $return .= '</tr>';
7870
        }
7871
        $return .= '<tr>';
7872
        $return .= '</tr>';
7873
        $return .= '</tbody>';
7874
        $return .= '</table>';
7875
        $return .= '</div>';
7876
        $return .= '<div class="form-group">';
7877
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
7878
            get_lang('Save prerequisites settings').'</button>';
7879
        $return .= '</form>';
7880
7881
        return $return;
7882
    }
7883
7884
    /**
7885
     * Return HTML list to allow prerequisites selection for lp.
7886
     *
7887
     * @return string HTML form
7888
     */
7889
    public function display_lp_prerequisites_list()
7890
    {
7891
        $course_id = api_get_course_int_id();
7892
        $lp_id = $this->lp_id;
7893
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
7894
7895
        // get current prerequisite
7896
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
7897
        $result = Database::query($sql);
7898
        $row = Database::fetch_array($result);
7899
        $prerequisiteId = $row['prerequisite'];
7900
        $session_id = api_get_session_id();
7901
        $session_condition = api_get_session_condition($session_id, true, true);
7902
        $sql = "SELECT * FROM $tbl_lp
7903
                WHERE c_id = $course_id $session_condition
7904
                ORDER BY display_order ";
7905
        $rs = Database::query($sql);
7906
        $return = '';
7907
        $return .= '<select name="prerequisites" class="form-control">';
7908
        $return .= '<option value="0">'.get_lang('none').'</option>';
7909
        if (Database::num_rows($rs) > 0) {
7910
            while ($row = Database::fetch_array($rs)) {
7911
                if ($row['iid'] == $lp_id) {
7912
                    continue;
7913
                }
7914
                $return .= '<option
7915
                    value="'.$row['iid'].'" '.(($row['iid'] == $prerequisiteId) ? ' selected ' : '').'>'.
7916
                    $row['name'].
7917
                    '</option>';
7918
            }
7919
        }
7920
        $return .= '</select>';
7921
7922
        return $return;
7923
    }
7924
7925
    /**
7926
     * Creates a list with all the documents in it.
7927
     *
7928
     * @param bool $showInvisibleFiles
7929
     *
7930
     * @throws Exception
7931
     * @throws HTML_QuickForm_Error
7932
     *
7933
     * @return string
7934
     */
7935
    public function get_documents($showInvisibleFiles = false)
7936
    {
7937
        $course_info = api_get_course_info();
7938
        $sessionId = api_get_session_id();
7939
        $documentTree = DocumentManager::get_document_preview(
7940
            $course_info,
7941
            $this->lp_id,
7942
            null,
7943
            $sessionId,
7944
            true,
7945
            null,
7946
            null,
7947
            $showInvisibleFiles,
7948
            true
7949
        );
7950
7951
        $headers = [
7952
            get_lang('Files'),
7953
            get_lang('CreateTheDocument'),
7954
            get_lang('CreateReadOutText'),
7955
            get_lang('Upload'),
7956
        ];
7957
7958
        $form = new FormValidator(
7959
            'form_upload',
7960
            'POST',
7961
            $this->getCurrentBuildingModeURL(),
7962
            '',
7963
            ['enctype' => 'multipart/form-data']
7964
        );
7965
7966
        $folders = DocumentManager::get_all_document_folders(
7967
            api_get_course_info(),
7968
            0,
7969
            true
7970
        );
7971
7972
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
7973
7974
        DocumentManager::build_directory_selector(
7975
            $folders,
7976
            $lpPathInfo['id'],
7977
            [],
7978
            true,
7979
            $form,
7980
            'directory_parent_id'
7981
        );
7982
7983
        $group = [
7984
            $form->createElement(
7985
                'radio',
7986
                'if_exists',
7987
                get_lang('If file exists:'),
7988
                get_lang('Do nothing'),
7989
                'nothing'
7990
            ),
7991
            $form->createElement(
7992
                'radio',
7993
                'if_exists',
7994
                null,
7995
                get_lang('Overwrite the existing file'),
7996
                'overwrite'
7997
            ),
7998
            $form->createElement(
7999
                'radio',
8000
                'if_exists',
8001
                null,
8002
                get_lang('Rename the uploaded file if it exists'),
8003
                'rename'
8004
            ),
8005
        ];
8006
        $form->addGroup($group, null, get_lang('If file exists:'));
8007
8008
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
8009
        $defaultFileExistsOption = 'rename';
8010
        if (!empty($fileExistsOption)) {
8011
            $defaultFileExistsOption = $fileExistsOption;
8012
        }
8013
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
8014
8015
        // Check box options
8016
        $form->addElement(
8017
            'checkbox',
8018
            'unzip',
8019
            get_lang('Options'),
8020
            get_lang('Uncompress zip')
8021
        );
8022
8023
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
8024
        $form->addMultipleUpload($url);
8025
8026
        $lpItem = new CLpItem();
8027
        $lpItem->setItemType(TOOL_DOCUMENT);
8028
        $new = $this->displayDocumentForm('add', $lpItem);
8029
8030
        /*$lpItem = new CLpItem();
8031
        $lpItem->setItemType(TOOL_READOUT_TEXT);
8032
        $frmReadOutText = $this->displayDocumentForm('add');*/
8033
8034
        $headers = [
8035
            get_lang('Files'),
8036
            get_lang('Create a new document'),
8037
            get_lang('Create read-out text'),
8038
            get_lang('Upload'),
8039
        ];
8040
8041
        return Display::tabs(
8042
            $headers,
8043
            [$documentTree, $new, $form->returnForm()],
8044
            'subtab'
8045
        );
8046
    }
8047
8048
    /**
8049
     * Creates a list with all the exercises (quiz) in it.
8050
     *
8051
     * @return string
8052
     */
8053
    public function get_exercises()
8054
    {
8055
        $course_id = api_get_course_int_id();
8056
        $session_id = api_get_session_id();
8057
        $userInfo = api_get_user_info();
8058
8059
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
8060
        $condition_session = api_get_session_condition($session_id, true, true);
8061
        $setting = 'true' === api_get_setting('lp.show_invisible_exercise_in_lp_toc');
8062
8063
        $activeCondition = ' active <> -1 ';
8064
        if ($setting) {
8065
            $activeCondition = ' active = 1 ';
8066
        }
8067
8068
        $categoryCondition = '';
8069
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
8070
        if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
8071
            $categoryCondition = " AND exercise_category_id = $categoryId ";
8072
        }
8073
8074
        $keywordCondition = '';
8075
        $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
8076
8077
        if (!empty($keyword)) {
8078
            $keyword = Database::escape_string($keyword);
8079
            $keywordCondition = " AND title LIKE '%$keyword%' ";
8080
        }
8081
8082
        $sql_quiz = "SELECT * FROM $tbl_quiz
8083
                     WHERE
8084
                            c_id = $course_id AND
8085
                            $activeCondition
8086
                            $condition_session
8087
                            $categoryCondition
8088
                            $keywordCondition
8089
                     ORDER BY title ASC";
8090
        $res_quiz = Database::query($sql_quiz);
8091
8092
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
8093
8094
        // Create a search-box
8095
        $form = new FormValidator('search_simple', 'get', $currentUrl);
8096
        $form->addHidden('action', 'add_item');
8097
        $form->addHidden('type', 'step');
8098
        $form->addHidden('lp_id', $this->lp_id);
8099
        $form->addHidden('lp_build_selected', '2');
8100
8101
        $form->addCourseHiddenParams();
8102
        $form->addText(
8103
            'keyword',
8104
            get_lang('Search'),
8105
            false,
8106
            [
8107
                'aria-label' => get_lang('Search'),
8108
            ]
8109
        );
8110
8111
        if (api_get_configuration_value('allow_exercise_categories')) {
8112
            $manager = new ExerciseCategoryManager();
8113
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
8114
            if (!empty($options)) {
8115
                $form->addSelect(
8116
                    'category_id',
8117
                    get_lang('Category'),
8118
                    $options,
8119
                    ['placeholder' => get_lang('Please select an option')]
8120
                );
8121
            }
8122
        }
8123
8124
        $form->addButtonSearch(get_lang('Search'));
8125
        $return = $form->returnForm();
8126
8127
        $return .= '<ul class="lp_resource">';
8128
        $return .= '<li class="lp_resource_element">';
8129
        $return .= Display::return_icon('new_exercice.png');
8130
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
8131
            get_lang('New test').'</a>';
8132
        $return .= '</li>';
8133
8134
        $previewIcon = Display::return_icon(
8135
            'preview_view.png',
8136
            get_lang('Preview')
8137
        );
8138
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
8139
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8140
8141
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
8142
        $repo = Container::getQuizRepository();
8143
        $courseEntity = api_get_course_entity();
8144
        $sessionEntity = api_get_session_entity();
8145
        while ($row_quiz = Database::fetch_array($res_quiz)) {
8146
            $exerciseId = $row_quiz['iid'];
8147
            /** @var CQuiz $exercise */
8148
            $exercise = $repo->find($exerciseId);
8149
            $title = strip_tags(api_html_entity_decode($row_quiz['title']));
8150
8151
            $visibility = $exercise->isVisible($courseEntity, $sessionEntity);
8152
            /*$visibility = api_get_item_visibility(
8153
                ['real_id' => $course_id],
8154
                TOOL_QUIZ,
8155
                $row_quiz['iid'],
8156
                $session_id
8157
            );*/
8158
8159
            $link = Display::url(
8160
                $previewIcon,
8161
                $exerciseUrl.'&exerciseId='.$exerciseId,
8162
                ['target' => '_blank']
8163
            );
8164
            $return .= '<li class="lp_resource_element" data_id="'.$exerciseId.'" data_type="quiz" title="'.$title.'" >';
8165
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
8166
            $return .= $quizIcon;
8167
            $sessionStar = api_get_session_image(
8168
                $row_quiz['session_id'],
8169
                $userInfo['status']
8170
            );
8171
            $return .= Display::url(
8172
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
8173
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$exerciseId.'&lp_id='.$this->lp_id,
8174
                [
8175
                    'class' => false === $visibility ? 'moved text-muted' : 'moved',
8176
                ]
8177
            );
8178
            $return .= '</li>';
8179
        }
8180
8181
        $return .= '</ul>';
8182
8183
        return $return;
8184
    }
8185
8186
    /**
8187
     * Creates a list with all the links in it.
8188
     *
8189
     * @return string
8190
     */
8191
    public function get_links()
8192
    {
8193
        $sessionId = api_get_session_id();
8194
        $repo = Container::getLinkRepository();
8195
8196
        $course = api_get_course_entity();
8197
        $session = api_get_session_entity($sessionId);
8198
        $qb = $repo->getResourcesByCourse($course, $session);
8199
        /** @var CLink[] $links */
8200
        $links = $qb->getQuery()->getResult();
8201
8202
        $selfUrl = api_get_self();
8203
        $courseIdReq = api_get_cidreq();
8204
        $userInfo = api_get_user_info();
8205
8206
        $moveEverywhereIcon = Display::return_icon(
8207
            'move_everywhere.png',
8208
            get_lang('Move'),
8209
            [],
8210
            ICON_SIZE_TINY
8211
        );
8212
8213
        /*$condition_session = api_get_session_condition(
8214
            $session_id,
8215
            true,
8216
            true,
8217
            'link.session_id'
8218
        );
8219
8220
        $sql = "SELECT
8221
                    link.id as link_id,
8222
                    link.title as link_title,
8223
                    link.session_id as link_session_id,
8224
                    link.category_id as category_id,
8225
                    link_category.category_title as category_title
8226
                FROM $tbl_link as link
8227
                LEFT JOIN $linkCategoryTable as link_category
8228
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
8229
                WHERE link.c_id = $course_id $condition_session
8230
                ORDER BY link_category.category_title ASC, link.title ASC";
8231
        $result = Database::query($sql);*/
8232
        $categorizedLinks = [];
8233
        $categories = [];
8234
8235
        foreach ($links as $link) {
8236
            $categoryId = null !== $link->getCategory() ? $link->getCategory()->getIid() : 0;
8237
8238
            if (empty($categoryId)) {
8239
                $categories[0] = get_lang('Uncategorized');
8240
            } else {
8241
                $category = $link->getCategory();
8242
                $categories[$categoryId] = $category->getCategoryTitle();
8243
            }
8244
            $categorizedLinks[$categoryId][$link->getIid()] = $link;
8245
        }
8246
8247
        $linksHtmlCode =
8248
            '<script>
8249
            function toggle_tool(tool, id) {
8250
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
8251
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
8252
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
8253
                } else {
8254
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
8255
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
8256
                }
8257
            }
8258
        </script>
8259
8260
        <ul class="lp_resource">
8261
            <li class="lp_resource_element">
8262
                '.Display::return_icon('linksnew.gif').'
8263
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('Add a link').'">'.
8264
                get_lang('Add a link').'
8265
                </a>
8266
            </li>';
8267
8268
        foreach ($categorizedLinks as $categoryId => $links) {
8269
            $linkNodes = null;
8270
            /** @var CLink $link */
8271
            foreach ($links as $key => $link) {
8272
                $title = $link->getTitle();
8273
8274
                $linkUrl = Display::url(
8275
                    Display::return_icon('preview_view.png', get_lang('Preview')),
8276
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
8277
                    ['target' => '_blank']
8278
                );
8279
8280
                if ($link->isVisible($course, $session)) {
8281
                    //$sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
8282
                    $sessionStar = '';
8283
                    $linkNodes .=
8284
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
8285
                        <a class="moved" href="#">'.
8286
                            $moveEverywhereIcon.
8287
                        '</a>
8288
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
8289
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
8290
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
8291
                        Security::remove_XSS($title).$sessionStar.$linkUrl.
8292
                        '</a>
8293
                    </li>';
8294
                }
8295
            }
8296
            $linksHtmlCode .=
8297
                '<li>
8298
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
8299
                    <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
8300
                    align="absbottom" />
8301
                </a>
8302
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
8303
            </li>
8304
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
8305
        }
8306
        $linksHtmlCode .= '</ul>';
8307
8308
        return $linksHtmlCode;
8309
    }
8310
8311
    /**
8312
     * Creates a list with all the student publications in it.
8313
     *
8314
     * @return string
8315
     */
8316
    public function get_student_publications()
8317
    {
8318
        $return = '<ul class="lp_resource">';
8319
        $return .= '<li class="lp_resource_element">';
8320
        /*
8321
        $return .= Display::return_icon('works_new.gif');
8322
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
8323
            get_lang('Add the Assignments tool to the course').'</a>';
8324
        $return .= '</li>';*/
8325
8326
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
8327
        $works = getWorkListTeacher(0, 100, null, null, null);
8328
        if (!empty($works)) {
8329
            foreach ($works as $work) {
8330
                $link = Display::url(
8331
                    Display::return_icon('preview_view.png', get_lang('Preview')),
8332
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
8333
                    ['target' => '_blank']
8334
                );
8335
8336
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
8337
                $return .= '<a class="moved" href="#">';
8338
                $return .= Display::return_icon(
8339
                    'move_everywhere.png',
8340
                    get_lang('Move'),
8341
                    [],
8342
                    ICON_SIZE_TINY
8343
                );
8344
                $return .= '</a> ';
8345
8346
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
8347
                $return .= ' <a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&file='.$work['iid'].'&lp_id='.$this->lp_id.'">'.
8348
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
8349
                </a>';
8350
8351
                $return .= '</li>';
8352
            }
8353
        }
8354
8355
        $return .= '</ul>';
8356
8357
        return $return;
8358
    }
8359
8360
    /**
8361
     * Creates a list with all the forums in it.
8362
     *
8363
     * @return string
8364
     */
8365
    public function get_forums()
8366
    {
8367
        require_once '../forum/forumfunction.inc.php';
8368
8369
        $forumCategories = get_forum_categories();
8370
        $forumsInNoCategory = get_forums_in_category(0);
8371
        if (!empty($forumsInNoCategory)) {
8372
            $forumCategories = array_merge(
8373
                $forumCategories,
8374
                [
8375
                    [
8376
                        'cat_id' => 0,
8377
                        'session_id' => 0,
8378
                        'visibility' => 1,
8379
                        'cat_comment' => null,
8380
                    ],
8381
                ]
8382
            );
8383
        }
8384
8385
        $a_forums = [];
8386
        $courseEntity = api_get_course_entity(api_get_course_int_id());
8387
        $sessionEntity = api_get_session_entity(api_get_session_id());
8388
8389
        foreach ($forumCategories as $forumCategory) {
8390
            // The forums in this category.
8391
            $forumsInCategory = get_forums_in_category($forumCategory->getIid());
8392
            if (!empty($forumsInCategory)) {
8393
                foreach ($forumsInCategory as $forum) {
8394
                    if ($forum->isVisible($courseEntity, $sessionEntity)) {
8395
                        $a_forums[] = $forum;
8396
                    }
8397
                }
8398
            }
8399
        }
8400
8401
        $return = '<ul class="lp_resource">';
8402
8403
        // First add link
8404
        $return .= '<li class="lp_resource_element">';
8405
        $return .= Display::return_icon('new_forum.png');
8406
        $return .= Display::url(
8407
            get_lang('Create a new forum'),
8408
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
8409
                'action' => 'add',
8410
                'content' => 'forum',
8411
                'lp_id' => $this->lp_id,
8412
            ]),
8413
            ['title' => get_lang('Create a new forum')]
8414
        );
8415
        $return .= '</li>';
8416
8417
        $return .= '<script>
8418
            function toggle_forum(forum_id) {
8419
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
8420
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
8421
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
8422
                } else {
8423
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
8424
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
8425
                }
8426
            }
8427
        </script>';
8428
8429
        foreach ($a_forums as $forum) {
8430
            $forumId = $forum->getIid();
8431
            $title = Security::remove_XSS($forum->getForumTitle());
8432
            $link = Display::url(
8433
                Display::return_icon('preview_view.png', get_lang('Preview')),
8434
                api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forumId,
8435
                ['target' => '_blank']
8436
            );
8437
8438
            $return .= '<li class="lp_resource_element" data_id="'.$forumId.'" data_type="'.TOOL_FORUM.'" title="'.$title.'" >';
8439
            $return .= '<a class="moved" href="#">';
8440
            $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8441
            $return .= ' </a>';
8442
            $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
8443
            $return .= '<a onclick="javascript:toggle_forum('.$forumId.');" style="cursor:hand; vertical-align:middle">
8444
                            <img src="'.Display::returnIconPath('add.png').'" id="forum_'.$forumId.'_opener" align="absbottom" />
8445
                        </a>
8446
                        <a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_FORUM.'&forum_id='.$forumId.'&lp_id='.$this->lp_id.'" style="vertical-align:middle">'.
8447
                $title.' '.$link.'</a>';
8448
8449
            $return .= '</li>';
8450
8451
            $return .= '<div style="display:none" id="forum_'.$forumId.'_content">';
8452
            $threads = get_threads($forumId);
8453
            if (is_array($threads)) {
8454
                foreach ($threads as $thread) {
8455
                    $threadId = $thread->getIid();
8456
                    $link = Display::url(
8457
                        Display::return_icon('preview_view.png', get_lang('Preview')),
8458
                        api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forumId.'&thread='.$threadId,
8459
                        ['target' => '_blank']
8460
                    );
8461
8462
                    $return .= '<li class="lp_resource_element" data_id="'.$thread->getIid().'" data_type="'.TOOL_THREAD.'" title="'.$thread->getThreadTitle().'" >';
8463
                    $return .= '&nbsp;<a class="moved" href="#">';
8464
                    $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8465
                    $return .= ' </a>';
8466
                    $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
8467
                    $return .= '<a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_THREAD.'&thread_id='.$threadId.'&lp_id='.$this->lp_id.'">'.
8468
                        Security::remove_XSS($thread->getThreadTitle()).' '.$link.'</a>';
8469
                    $return .= '</li>';
8470
                }
8471
            }
8472
            $return .= '</div>';
8473
        }
8474
        $return .= '</ul>';
8475
8476
        return $return;
8477
    }
8478
8479
    /**
8480
     * // TODO: The output encoding should be equal to the system encoding.
8481
     *
8482
     * Exports the learning path as a SCORM package. This is the main function that
8483
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
8484
     * whole thing and returns the zip.
8485
     *
8486
     * This method needs to be called in PHP5, as it will fail with non-adequate
8487
     * XML package (like the ones for PHP4), and it is *not* a static method, so
8488
     * you need to call it on a learnpath object.
8489
     *
8490
     * @TODO The method might be redefined later on in the scorm class itself to avoid
8491
     * creating a SCORM structure if there is one already. However, if the initial SCORM
8492
     * path has been modified, it should use the generic method here below.
8493
     *
8494
     * @return string Returns the zip package string, or null if error
8495
     */
8496
    public function scormExport()
8497
    {
8498
        api_set_more_memory_and_time_limits();
8499
8500
        $_course = api_get_course_info();
8501
        $course_id = $_course['real_id'];
8502
        // Create the zip handler (this will remain available throughout the method).
8503
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
8504
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
8505
        $temp_dir_short = uniqid('scorm_export', true);
8506
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
8507
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
8508
        $zip_folder = new PclZip($temp_zip_file);
8509
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
8510
        $root_path = $main_path = api_get_path(SYS_PATH);
8511
        $files_cleanup = [];
8512
8513
        // Place to temporarily stash the zip file.
8514
        // create the temp dir if it doesn't exist
8515
        // or do a cleanup before creating the zip file.
8516
        if (!is_dir($temp_zip_dir)) {
8517
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
8518
        } else {
8519
            // Cleanup: Check the temp dir for old files and delete them.
8520
            $handle = opendir($temp_zip_dir);
8521
            while (false !== ($file = readdir($handle))) {
8522
                if ('.' != $file && '..' != $file) {
8523
                    unlink("$temp_zip_dir/$file");
8524
                }
8525
            }
8526
            closedir($handle);
8527
        }
8528
        $zip_files = $zip_files_abs = $zip_files_dist = [];
8529
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
8530
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
8531
        ) {
8532
            // Remove the possible . at the end of the path.
8533
            $dest_path_to_lp = '.' == substr($this->path, -1) ? substr($this->path, 0, -1) : $this->path;
8534
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
8535
            mkdir(
8536
                $dest_path_to_scorm_folder,
8537
                api_get_permissions_for_new_directories(),
8538
                true
8539
            );
8540
            copyr(
8541
                $current_course_path.'/scorm/'.$this->path,
8542
                $dest_path_to_scorm_folder,
8543
                ['imsmanifest'],
8544
                $zip_files
8545
            );
8546
        }
8547
8548
        // Build a dummy imsmanifest structure.
8549
        // Do not add to the zip yet (we still need it).
8550
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
8551
        // Aggregation Model official document, section "2.3 Content Packaging".
8552
        // We are going to build a UTF-8 encoded manifest.
8553
        // Later we will recode it to the desired (and supported) encoding.
8554
        $xmldoc = new DOMDocument('1.0');
8555
        $root = $xmldoc->createElement('manifest');
8556
        $root->setAttribute('identifier', 'SingleCourseManifest');
8557
        $root->setAttribute('version', '1.1');
8558
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
8559
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
8560
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
8561
        $root->setAttribute(
8562
            'xsi:schemaLocation',
8563
            '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'
8564
        );
8565
        // Build mandatory sub-root container elements.
8566
        $metadata = $xmldoc->createElement('metadata');
8567
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
8568
        $metadata->appendChild($md_schema);
8569
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
8570
        $metadata->appendChild($md_schemaversion);
8571
        $root->appendChild($metadata);
8572
8573
        $organizations = $xmldoc->createElement('organizations');
8574
        $resources = $xmldoc->createElement('resources');
8575
8576
        // Build the only organization we will use in building our learnpaths.
8577
        $organizations->setAttribute('default', 'chamilo_scorm_export');
8578
        $organization = $xmldoc->createElement('organization');
8579
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
8580
        // To set the title of the SCORM entity (=organization), we take the name given
8581
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
8582
        // learning path charset) as it is the encoding that defines how it is stored
8583
        // in the database. Then we convert it to HTML entities again as the "&" character
8584
        // alone is not authorized in XML (must be &amp;).
8585
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
8586
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
8587
        $organization->appendChild($org_title);
8588
        $folder_name = 'document';
8589
8590
        // Removes the learning_path/scorm_folder path when exporting see #4841
8591
        $path_to_remove = '';
8592
        $path_to_replace = '';
8593
        $result = $this->generate_lp_folder($_course);
8594
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
8595
            $path_to_remove = 'document'.$result['dir'];
8596
            $path_to_replace = $folder_name.'/';
8597
        }
8598
8599
        // Fixes chamilo scorm exports
8600
        if ('chamilo_scorm_export' === $this->ref) {
8601
            $path_to_remove = 'scorm/'.$this->path.'/document/';
8602
        }
8603
8604
        // For each element, add it to the imsmanifest structure, then add it to the zip.
8605
        $link_updates = [];
8606
        $links_to_create = [];
8607
        foreach ($this->ordered_items as $index => $itemId) {
8608
            /** @var learnpathItem $item */
8609
            $item = $this->items[$itemId];
8610
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
8611
                // Get included documents from this item.
8612
                if ('sco' === $item->type) {
8613
                    $inc_docs = $item->get_resources_from_source(
8614
                        null,
8615
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
8616
                    );
8617
                } else {
8618
                    $inc_docs = $item->get_resources_from_source();
8619
                }
8620
8621
                // Give a child element <item> to the <organization> element.
8622
                $my_item_id = $item->get_id();
8623
                $my_item = $xmldoc->createElement('item');
8624
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
8625
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
8626
                $my_item->setAttribute('isvisible', 'true');
8627
                // Give a child element <title> to the <item> element.
8628
                $my_title = $xmldoc->createElement(
8629
                    'title',
8630
                    htmlspecialchars(
8631
                        api_utf8_encode($item->get_title()),
8632
                        ENT_QUOTES,
8633
                        'UTF-8'
8634
                    )
8635
                );
8636
                $my_item->appendChild($my_title);
8637
                // Give a child element <adlcp:prerequisites> to the <item> element.
8638
                $my_prereqs = $xmldoc->createElement(
8639
                    'adlcp:prerequisites',
8640
                    $this->get_scorm_prereq_string($my_item_id)
8641
                );
8642
                $my_prereqs->setAttribute('type', 'aicc_script');
8643
                $my_item->appendChild($my_prereqs);
8644
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
8645
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
8646
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
8647
                //$xmldoc->createElement('adlcp:timelimitaction','');
8648
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
8649
                //$xmldoc->createElement('adlcp:datafromlms','');
8650
                // Give a child element <adlcp:masteryscore> to the <item> element.
8651
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
8652
                $my_item->appendChild($my_masteryscore);
8653
8654
                // Attach this item to the organization element or hits parent if there is one.
8655
                if (!empty($item->parent) && 0 != $item->parent) {
8656
                    $children = $organization->childNodes;
8657
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
8658
                    if (is_object($possible_parent)) {
8659
                        $possible_parent->appendChild($my_item);
8660
                    } else {
8661
                        if ($this->debug > 0) {
8662
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
8663
                        }
8664
                    }
8665
                } else {
8666
                    if ($this->debug > 0) {
8667
                        error_log('No parent');
8668
                    }
8669
                    $organization->appendChild($my_item);
8670
                }
8671
8672
                // Get the path of the file(s) from the course directory root.
8673
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
8674
                $my_xml_file_path = $my_file_path;
8675
                if (!empty($path_to_remove)) {
8676
                    // From docs
8677
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
8678
8679
                    // From quiz
8680
                    if ('chamilo_scorm_export' === $this->ref) {
8681
                        $path_to_remove = 'scorm/'.$this->path.'/';
8682
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
8683
                    }
8684
                }
8685
8686
                $my_sub_dir = dirname($my_file_path);
8687
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
8688
                $my_xml_sub_dir = $my_sub_dir;
8689
                // Give a <resource> child to the <resources> element
8690
                $my_resource = $xmldoc->createElement('resource');
8691
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
8692
                $my_resource->setAttribute('type', 'webcontent');
8693
                $my_resource->setAttribute('href', $my_xml_file_path);
8694
                // adlcp:scormtype can be either 'sco' or 'asset'.
8695
                if ('sco' === $item->type) {
8696
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
8697
                } else {
8698
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
8699
                }
8700
                // xml:base is the base directory to find the files declared in this resource.
8701
                $my_resource->setAttribute('xml:base', '');
8702
                // Give a <file> child to the <resource> element.
8703
                $my_file = $xmldoc->createElement('file');
8704
                $my_file->setAttribute('href', $my_xml_file_path);
8705
                $my_resource->appendChild($my_file);
8706
8707
                // Dependency to other files - not yet supported.
8708
                $i = 1;
8709
                if ($inc_docs) {
8710
                    foreach ($inc_docs as $doc_info) {
8711
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
8712
                            continue;
8713
                        }
8714
                        $my_dep = $xmldoc->createElement('resource');
8715
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
8716
                        $my_dep->setAttribute('identifier', $res_id);
8717
                        $my_dep->setAttribute('type', 'webcontent');
8718
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
8719
                        $my_dep_file = $xmldoc->createElement('file');
8720
                        // Check type of URL.
8721
                        if ('remote' == $doc_info[1]) {
8722
                            // Remote file. Save url as is.
8723
                            $my_dep_file->setAttribute('href', $doc_info[0]);
8724
                            $my_dep->setAttribute('xml:base', '');
8725
                        } elseif ('local' === $doc_info[1]) {
8726
                            switch ($doc_info[2]) {
8727
                                case 'url':
8728
                                    // Local URL - save path as url for now, don't zip file.
8729
                                    $abs_path = api_get_path(SYS_PATH).
8730
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
8731
                                    $current_dir = dirname($abs_path);
8732
                                    $current_dir = str_replace('\\', '/', $current_dir);
8733
                                    $file_path = realpath($abs_path);
8734
                                    $file_path = str_replace('\\', '/', $file_path);
8735
                                    $my_dep_file->setAttribute('href', $file_path);
8736
                                    $my_dep->setAttribute('xml:base', '');
8737
                                    if (false !== strstr($file_path, $main_path)) {
8738
                                        // The calculated real path is really inside Chamilo's root path.
8739
                                        // Reduce file path to what's under the DocumentRoot.
8740
                                        $replace = $file_path;
8741
                                        $file_path = substr($file_path, strlen($root_path) - 1);
8742
                                        $destinationFile = $file_path;
8743
8744
                                        if (false !== strstr($file_path, 'upload/users')) {
8745
                                            $pos = strpos($file_path, 'my_files/');
8746
                                            if (false !== $pos) {
8747
                                                $onlyDirectory = str_replace(
8748
                                                    'upload/users/',
8749
                                                    '',
8750
                                                    substr($file_path, $pos, strlen($file_path))
8751
                                                );
8752
                                            }
8753
                                            $replace = $onlyDirectory;
8754
                                            $destinationFile = $replace;
8755
                                        }
8756
                                        $zip_files_abs[] = $file_path;
8757
                                        $link_updates[$my_file_path][] = [
8758
                                            'orig' => $doc_info[0],
8759
                                            'dest' => $destinationFile,
8760
                                            'replace' => $replace,
8761
                                        ];
8762
                                        $my_dep_file->setAttribute('href', $file_path);
8763
                                        $my_dep->setAttribute('xml:base', '');
8764
                                    } elseif (empty($file_path)) {
8765
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
8766
                                        $file_path = str_replace('//', '/', $file_path);
8767
                                        if (file_exists($file_path)) {
8768
                                            // We get the relative path.
8769
                                            $file_path = substr($file_path, strlen($current_dir));
8770
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
8771
                                            $link_updates[$my_file_path][] = [
8772
                                                'orig' => $doc_info[0],
8773
                                                'dest' => $file_path,
8774
                                            ];
8775
                                            $my_dep_file->setAttribute('href', $file_path);
8776
                                            $my_dep->setAttribute('xml:base', '');
8777
                                        }
8778
                                    }
8779
                                    break;
8780
                                case 'abs':
8781
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
8782
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
8783
                                    $my_dep->setAttribute('xml:base', '');
8784
8785
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
8786
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
8787
                                    $abs_img_path_without_subdir = $doc_info[0];
8788
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
8789
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
8790
                                    if (0 === $pos) {
8791
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
8792
                                    }
8793
8794
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
8795
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
8796
8797
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
8798
                                    $cur_path = '/' == substr($current_course_path, -1) ? $current_course_path : $current_course_path.'/';
8799
                                    // Check if the current document is in that path.
8800
                                    if (false !== strstr($file_path, $cur_path)) {
8801
                                        $destinationFile = substr($file_path, strlen($cur_path));
8802
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
8803
8804
                                        $fileToTest = $cur_path.$my_file_path;
8805
                                        if (!empty($path_to_remove)) {
8806
                                            $fileToTest = str_replace(
8807
                                                $path_to_remove.'/',
8808
                                                $path_to_replace,
8809
                                                $cur_path.$my_file_path
8810
                                            );
8811
                                        }
8812
8813
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
8814
8815
                                        // Put the current document in the zip (this array is the array
8816
                                        // that will manage documents already in the course folder - relative).
8817
                                        $zip_files[] = $filePathNoCoursePart;
8818
                                        // Update the links to the current document in the
8819
                                        // containing document (make them relative).
8820
                                        $link_updates[$my_file_path][] = [
8821
                                            'orig' => $doc_info[0],
8822
                                            'dest' => $destinationFile,
8823
                                            'replace' => $relative_path,
8824
                                        ];
8825
8826
                                        $my_dep_file->setAttribute('href', $file_path);
8827
                                        $my_dep->setAttribute('xml:base', '');
8828
                                    } elseif (false !== strstr($file_path, $main_path)) {
8829
                                        // The calculated real path is really inside Chamilo's root path.
8830
                                        // Reduce file path to what's under the DocumentRoot.
8831
                                        $file_path = substr($file_path, strlen($root_path));
8832
                                        $zip_files_abs[] = $file_path;
8833
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
8834
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
8835
                                        $my_dep->setAttribute('xml:base', '');
8836
                                    } elseif (empty($file_path)) {
8837
                                        // Probably this is an image inside "/main" directory
8838
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
8839
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
8840
8841
                                        if (file_exists($file_path)) {
8842
                                            if (false !== strstr($file_path, 'main/default_course_document')) {
8843
                                                // We get the relative path.
8844
                                                $pos = strpos($file_path, 'main/default_course_document/');
8845
                                                if (false !== $pos) {
8846
                                                    $onlyDirectory = str_replace(
8847
                                                        'main/default_course_document/',
8848
                                                        '',
8849
                                                        substr($file_path, $pos, strlen($file_path))
8850
                                                    );
8851
                                                }
8852
8853
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
8854
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
8855
                                                $zip_files_abs[] = $fileAbs;
8856
                                                $link_updates[$my_file_path][] = [
8857
                                                    'orig' => $doc_info[0],
8858
                                                    'dest' => $destinationFile,
8859
                                                ];
8860
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
8861
                                                $my_dep->setAttribute('xml:base', '');
8862
                                            }
8863
                                        }
8864
                                    }
8865
                                    break;
8866
                                case 'rel':
8867
                                    // Path relative to the current document.
8868
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
8869
                                    if ('..' === substr($doc_info[0], 0, 2)) {
8870
                                        // Relative path going up.
8871
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
8872
                                        $current_dir = str_replace('\\', '/', $current_dir);
8873
                                        $file_path = realpath($current_dir.$doc_info[0]);
8874
                                        $file_path = str_replace('\\', '/', $file_path);
8875
                                        if (false !== strstr($file_path, $main_path)) {
8876
                                            // The calculated real path is really inside Chamilo's root path.
8877
                                            // Reduce file path to what's under the DocumentRoot.
8878
                                            $file_path = substr($file_path, strlen($root_path));
8879
                                            $zip_files_abs[] = $file_path;
8880
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
8881
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
8882
                                            $my_dep->setAttribute('xml:base', '');
8883
                                        }
8884
                                    } else {
8885
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
8886
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
8887
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
8888
                                    }
8889
                                    break;
8890
                                default:
8891
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
8892
                                    $my_dep->setAttribute('xml:base', '');
8893
                                    break;
8894
                            }
8895
                        }
8896
                        $my_dep->appendChild($my_dep_file);
8897
                        $resources->appendChild($my_dep);
8898
                        $dependency = $xmldoc->createElement('dependency');
8899
                        $dependency->setAttribute('identifierref', $res_id);
8900
                        $my_resource->appendChild($dependency);
8901
                        $i++;
8902
                    }
8903
                }
8904
                $resources->appendChild($my_resource);
8905
                $zip_files[] = $my_file_path;
8906
            } else {
8907
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
8908
                switch ($item->type) {
8909
                    case TOOL_LINK:
8910
                        $my_item = $xmldoc->createElement('item');
8911
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
8912
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
8913
                        $my_item->setAttribute('isvisible', 'true');
8914
                        // Give a child element <title> to the <item> element.
8915
                        $my_title = $xmldoc->createElement(
8916
                            'title',
8917
                            htmlspecialchars(
8918
                                api_utf8_encode($item->get_title()),
8919
                                ENT_QUOTES,
8920
                                'UTF-8'
8921
                            )
8922
                        );
8923
                        $my_item->appendChild($my_title);
8924
                        // Give a child element <adlcp:prerequisites> to the <item> element.
8925
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
8926
                        $my_prereqs->setAttribute('type', 'aicc_script');
8927
                        $my_item->appendChild($my_prereqs);
8928
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
8929
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
8930
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
8931
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
8932
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
8933
                        //$xmldoc->createElement('adlcp:datafromlms', '');
8934
                        // Give a child element <adlcp:masteryscore> to the <item> element.
8935
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
8936
                        $my_item->appendChild($my_masteryscore);
8937
8938
                        // Attach this item to the organization element or its parent if there is one.
8939
                        if (!empty($item->parent) && 0 != $item->parent) {
8940
                            $children = $organization->childNodes;
8941
                            for ($i = 0; $i < $children->length; $i++) {
8942
                                $item_temp = $children->item($i);
8943
                                if ('item' == $item_temp->nodeName) {
8944
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
8945
                                        $item_temp->appendChild($my_item);
8946
                                    }
8947
                                }
8948
                            }
8949
                        } else {
8950
                            $organization->appendChild($my_item);
8951
                        }
8952
8953
                        $my_file_path = 'link_'.$item->get_id().'.html';
8954
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
8955
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
8956
                        $rs = Database::query($sql);
8957
                        if ($link = Database::fetch_array($rs)) {
8958
                            $url = $link['url'];
8959
                            $title = stripslashes($link['title']);
8960
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
8961
                            $my_xml_file_path = $my_file_path;
8962
                            $my_sub_dir = dirname($my_file_path);
8963
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
8964
                            $my_xml_sub_dir = $my_sub_dir;
8965
                            // Give a <resource> child to the <resources> element.
8966
                            $my_resource = $xmldoc->createElement('resource');
8967
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
8968
                            $my_resource->setAttribute('type', 'webcontent');
8969
                            $my_resource->setAttribute('href', $my_xml_file_path);
8970
                            // adlcp:scormtype can be either 'sco' or 'asset'.
8971
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
8972
                            // xml:base is the base directory to find the files declared in this resource.
8973
                            $my_resource->setAttribute('xml:base', '');
8974
                            // give a <file> child to the <resource> element.
8975
                            $my_file = $xmldoc->createElement('file');
8976
                            $my_file->setAttribute('href', $my_xml_file_path);
8977
                            $my_resource->appendChild($my_file);
8978
                            $resources->appendChild($my_resource);
8979
                        }
8980
                        break;
8981
                    case TOOL_QUIZ:
8982
                        $exe_id = $item->path;
8983
                        // Should be using ref when everything will be cleaned up in this regard.
8984
                        $exe = new Exercise();
8985
                        $exe->read($exe_id);
8986
                        $my_item = $xmldoc->createElement('item');
8987
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
8988
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
8989
                        $my_item->setAttribute('isvisible', 'true');
8990
                        // Give a child element <title> to the <item> element.
8991
                        $my_title = $xmldoc->createElement(
8992
                            'title',
8993
                            htmlspecialchars(
8994
                                api_utf8_encode($item->get_title()),
8995
                                ENT_QUOTES,
8996
                                'UTF-8'
8997
                            )
8998
                        );
8999
                        $my_item->appendChild($my_title);
9000
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
9001
                        $my_item->appendChild($my_max_score);
9002
                        // Give a child element <adlcp:prerequisites> to the <item> element.
9003
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
9004
                        $my_prereqs->setAttribute('type', 'aicc_script');
9005
                        $my_item->appendChild($my_prereqs);
9006
                        // Give a child element <adlcp:masteryscore> to the <item> element.
9007
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
9008
                        $my_item->appendChild($my_masteryscore);
9009
9010
                        // Attach this item to the organization element or hits parent if there is one.
9011
                        if (!empty($item->parent) && 0 != $item->parent) {
9012
                            $children = $organization->childNodes;
9013
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
9014
                            if ($possible_parent) {
9015
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
9016
                                    $possible_parent->appendChild($my_item);
9017
                                }
9018
                            }
9019
                        } else {
9020
                            $organization->appendChild($my_item);
9021
                        }
9022
9023
                        // Get the path of the file(s) from the course directory root
9024
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
9025
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
9026
                        // Write the contents of the exported exercise into a (big) html file
9027
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
9028
                        $scormExercise = new ScormExercise($exe, true);
9029
                        $contents = $scormExercise->export();
9030
9031
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
9032
                        $res = file_put_contents($tmp_file_path, $contents);
9033
                        if (false === $res) {
9034
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
9035
                        }
9036
                        $files_cleanup[] = $tmp_file_path;
9037
                        $my_xml_file_path = $my_file_path;
9038
                        $my_sub_dir = dirname($my_file_path);
9039
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
9040
                        $my_xml_sub_dir = $my_sub_dir;
9041
                        // Give a <resource> child to the <resources> element.
9042
                        $my_resource = $xmldoc->createElement('resource');
9043
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
9044
                        $my_resource->setAttribute('type', 'webcontent');
9045
                        $my_resource->setAttribute('href', $my_xml_file_path);
9046
                        // adlcp:scormtype can be either 'sco' or 'asset'.
9047
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
9048
                        // xml:base is the base directory to find the files declared in this resource.
9049
                        $my_resource->setAttribute('xml:base', '');
9050
                        // Give a <file> child to the <resource> element.
9051
                        $my_file = $xmldoc->createElement('file');
9052
                        $my_file->setAttribute('href', $my_xml_file_path);
9053
                        $my_resource->appendChild($my_file);
9054
9055
                        // Get included docs.
9056
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
9057
9058
                        // Dependency to other files - not yet supported.
9059
                        $i = 1;
9060
                        foreach ($inc_docs as $doc_info) {
9061
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
9062
                                continue;
9063
                            }
9064
                            $my_dep = $xmldoc->createElement('resource');
9065
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
9066
                            $my_dep->setAttribute('identifier', $res_id);
9067
                            $my_dep->setAttribute('type', 'webcontent');
9068
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
9069
                            $my_dep_file = $xmldoc->createElement('file');
9070
                            // Check type of URL.
9071
                            if ('remote' == $doc_info[1]) {
9072
                                // Remote file. Save url as is.
9073
                                $my_dep_file->setAttribute('href', $doc_info[0]);
9074
                                $my_dep->setAttribute('xml:base', '');
9075
                            } elseif ('local' == $doc_info[1]) {
9076
                                switch ($doc_info[2]) {
9077
                                    case 'url': // Local URL - save path as url for now, don't zip file.
9078
                                        // Save file but as local file (retrieve from URL).
9079
                                        $abs_path = api_get_path(SYS_PATH).
9080
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
9081
                                        $current_dir = dirname($abs_path);
9082
                                        $current_dir = str_replace('\\', '/', $current_dir);
9083
                                        $file_path = realpath($abs_path);
9084
                                        $file_path = str_replace('\\', '/', $file_path);
9085
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
9086
                                        $my_dep->setAttribute('xml:base', '');
9087
                                        if (false !== strstr($file_path, $main_path)) {
9088
                                            // The calculated real path is really inside the chamilo root path.
9089
                                            // Reduce file path to what's under the DocumentRoot.
9090
                                            $file_path = substr($file_path, strlen($root_path));
9091
                                            $zip_files_abs[] = $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
                                        } elseif (empty($file_path)) {
9099
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
9100
                                            $file_path = str_replace('//', '/', $file_path);
9101
                                            if (file_exists($file_path)) {
9102
                                                $file_path = substr($file_path, strlen($current_dir));
9103
                                                // We get the relative path.
9104
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
9105
                                                $link_updates[$my_file_path][] = [
9106
                                                    'orig' => $doc_info[0],
9107
                                                    'dest' => 'document/'.$file_path,
9108
                                                ];
9109
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9110
                                                $my_dep->setAttribute('xml:base', '');
9111
                                            }
9112
                                        }
9113
                                        break;
9114
                                    case 'abs':
9115
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
9116
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
9117
                                        $current_dir = str_replace('\\', '/', $current_dir);
9118
                                        $file_path = realpath($doc_info[0]);
9119
                                        $file_path = str_replace('\\', '/', $file_path);
9120
                                        $my_dep_file->setAttribute('href', $file_path);
9121
                                        $my_dep->setAttribute('xml:base', '');
9122
9123
                                        if (false !== strstr($file_path, $main_path)) {
9124
                                            // The calculated real path is really inside the chamilo root path.
9125
                                            // Reduce file path to what's under the DocumentRoot.
9126
                                            $file_path = substr($file_path, strlen($root_path));
9127
                                            $zip_files_abs[] = $file_path;
9128
                                            $link_updates[$my_file_path][] = [
9129
                                                'orig' => $doc_info[0],
9130
                                                'dest' => $file_path,
9131
                                            ];
9132
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
9133
                                            $my_dep->setAttribute('xml:base', '');
9134
                                        } elseif (empty($file_path)) {
9135
                                            $docSysPartPath = str_replace(
9136
                                                api_get_path(REL_COURSE_PATH),
9137
                                                '',
9138
                                                $doc_info[0]
9139
                                            );
9140
9141
                                            $docSysPartPathNoCourseCode = str_replace(
9142
                                                $_course['directory'].'/',
9143
                                                '',
9144
                                                $docSysPartPath
9145
                                            );
9146
9147
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
9148
                                            if (file_exists($docSysPath)) {
9149
                                                $file_path = $docSysPartPathNoCourseCode;
9150
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
9151
                                                $link_updates[$my_file_path][] = [
9152
                                                    'orig' => $doc_info[0],
9153
                                                    'dest' => $file_path,
9154
                                                ];
9155
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9156
                                                $my_dep->setAttribute('xml:base', '');
9157
                                            }
9158
                                        }
9159
                                        break;
9160
                                    case 'rel':
9161
                                        // Path relative to the current document. Save xml:base as current document's
9162
                                        // directory and save file in zip as subdir.file_path
9163
                                        if ('..' === substr($doc_info[0], 0, 2)) {
9164
                                            // Relative path going up.
9165
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
9166
                                            $current_dir = str_replace('\\', '/', $current_dir);
9167
                                            $file_path = realpath($current_dir.$doc_info[0]);
9168
                                            $file_path = str_replace('\\', '/', $file_path);
9169
                                            if (false !== strstr($file_path, $main_path)) {
9170
                                                // The calculated real path is really inside Chamilo's root path.
9171
                                                // Reduce file path to what's under the DocumentRoot.
9172
9173
                                                $file_path = substr($file_path, strlen($root_path));
9174
                                                $file_path_dest = $file_path;
9175
9176
                                                // File path is courses/CHAMILO/document/....
9177
                                                $info_file_path = explode('/', $file_path);
9178
                                                if ('courses' == $info_file_path[0]) {
9179
                                                    // Add character "/" in file path.
9180
                                                    $file_path_dest = 'document/'.$file_path;
9181
                                                }
9182
                                                $zip_files_abs[] = $file_path;
9183
9184
                                                $link_updates[$my_file_path][] = [
9185
                                                    'orig' => $doc_info[0],
9186
                                                    'dest' => $file_path_dest,
9187
                                                ];
9188
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9189
                                                $my_dep->setAttribute('xml:base', '');
9190
                                            }
9191
                                        } else {
9192
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
9193
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
9194
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
9195
                                        }
9196
                                        break;
9197
                                    default:
9198
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
9199
                                        $my_dep->setAttribute('xml:base', '');
9200
                                        break;
9201
                                }
9202
                            }
9203
                            $my_dep->appendChild($my_dep_file);
9204
                            $resources->appendChild($my_dep);
9205
                            $dependency = $xmldoc->createElement('dependency');
9206
                            $dependency->setAttribute('identifierref', $res_id);
9207
                            $my_resource->appendChild($dependency);
9208
                            $i++;
9209
                        }
9210
                        $resources->appendChild($my_resource);
9211
                        $zip_files[] = $my_file_path;
9212
                        break;
9213
                    default:
9214
                        // Get the path of the file(s) from the course directory root
9215
                        $my_file_path = 'non_exportable.html';
9216
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
9217
                        $my_xml_file_path = $my_file_path;
9218
                        $my_sub_dir = dirname($my_file_path);
9219
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
9220
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
9221
                        $my_xml_sub_dir = $my_sub_dir;
9222
                        // Give a <resource> child to the <resources> element.
9223
                        $my_resource = $xmldoc->createElement('resource');
9224
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
9225
                        $my_resource->setAttribute('type', 'webcontent');
9226
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
9227
                        // adlcp:scormtype can be either 'sco' or 'asset'.
9228
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
9229
                        // xml:base is the base directory to find the files declared in this resource.
9230
                        $my_resource->setAttribute('xml:base', '');
9231
                        // Give a <file> child to the <resource> element.
9232
                        $my_file = $xmldoc->createElement('file');
9233
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
9234
                        $my_resource->appendChild($my_file);
9235
                        $resources->appendChild($my_resource);
9236
                        break;
9237
                }
9238
            }
9239
        }
9240
        $organizations->appendChild($organization);
9241
        $root->appendChild($organizations);
9242
        $root->appendChild($resources);
9243
        $xmldoc->appendChild($root);
9244
9245
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
9246
9247
        // then add the file to the zip, then destroy the file (this is done automatically).
9248
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
9249
        foreach ($zip_files as $file_path) {
9250
            if (empty($file_path)) {
9251
                continue;
9252
            }
9253
9254
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
9255
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
9256
9257
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
9258
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
9259
            }
9260
9261
            $this->create_path($dest_file);
9262
            @copy($filePath, $dest_file);
9263
9264
            // Check if the file needs a link update.
9265
            if (in_array($file_path, array_keys($link_updates))) {
9266
                $string = file_get_contents($dest_file);
9267
                unlink($dest_file);
9268
                foreach ($link_updates[$file_path] as $old_new) {
9269
                    // This is an ugly hack that allows .flv files to be found by the flv player that
9270
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
9271
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
9272
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
9273
                    if ('flv' === substr($old_new['dest'], -3) &&
9274
                        'main/' === substr($old_new['dest'], 0, 5)
9275
                    ) {
9276
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
9277
                    } elseif ('flv' === substr($old_new['dest'], -3) &&
9278
                        'video/' === substr($old_new['dest'], 0, 6)
9279
                    ) {
9280
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
9281
                    }
9282
9283
                    // Fix to avoid problems with default_course_document
9284
                    if (false === strpos('main/default_course_document', $old_new['dest'])) {
9285
                        $newDestination = $old_new['dest'];
9286
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
9287
                            $newDestination = $old_new['replace'];
9288
                        }
9289
                    } else {
9290
                        $newDestination = str_replace('document/', '', $old_new['dest']);
9291
                    }
9292
                    $string = str_replace($old_new['orig'], $newDestination, $string);
9293
9294
                    // Add files inside the HTMLs
9295
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
9296
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
9297
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
9298
                        copy(
9299
                            $sys_course_path.$new_path,
9300
                            $destinationFile
9301
                        );
9302
                    }
9303
                }
9304
                file_put_contents($dest_file, $string);
9305
            }
9306
9307
            if (file_exists($filePath) && $copyAll) {
9308
                $extension = $this->get_extension($filePath);
9309
                if (in_array($extension, ['html', 'html'])) {
9310
                    $containerOrigin = dirname($filePath);
9311
                    $containerDestination = dirname($dest_file);
9312
9313
                    $finder = new Finder();
9314
                    $finder->files()->in($containerOrigin)
9315
                        ->notName('*_DELETED_*')
9316
                        ->exclude('share_folder')
9317
                        ->exclude('chat_files')
9318
                        ->exclude('certificates')
9319
                    ;
9320
9321
                    if (is_dir($containerOrigin) &&
9322
                        is_dir($containerDestination)
9323
                    ) {
9324
                        $fs = new Filesystem();
9325
                        $fs->mirror(
9326
                            $containerOrigin,
9327
                            $containerDestination,
9328
                            $finder
9329
                        );
9330
                    }
9331
                }
9332
            }
9333
        }
9334
9335
        foreach ($zip_files_abs as $file_path) {
9336
            if (empty($file_path)) {
9337
                continue;
9338
            }
9339
9340
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
9341
                continue;
9342
            }
9343
9344
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
9345
            if (false !== strstr($file_path, 'upload/users')) {
9346
                $pos = strpos($file_path, 'my_files/');
9347
                if (false !== $pos) {
9348
                    $onlyDirectory = str_replace(
9349
                        'upload/users/',
9350
                        '',
9351
                        substr($file_path, $pos, strlen($file_path))
9352
                    );
9353
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
9354
                }
9355
            }
9356
9357
            if (false !== strstr($file_path, 'default_course_document/')) {
9358
                $replace = str_replace('/main', '', $file_path);
9359
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
9360
            }
9361
9362
            if (empty($dest_file)) {
9363
                continue;
9364
            }
9365
9366
            $this->create_path($dest_file);
9367
            copy($main_path.$file_path, $dest_file);
9368
            // Check if the file needs a link update.
9369
            if (in_array($file_path, array_keys($link_updates))) {
9370
                $string = file_get_contents($dest_file);
9371
                unlink($dest_file);
9372
                foreach ($link_updates[$file_path] as $old_new) {
9373
                    // This is an ugly hack that allows .flv files to be found by the flv player that
9374
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
9375
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
9376
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
9377
                    if ('flv' == substr($old_new['dest'], -3) &&
9378
                        'main/' == substr($old_new['dest'], 0, 5)
9379
                    ) {
9380
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
9381
                    }
9382
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
9383
                }
9384
                file_put_contents($dest_file, $string);
9385
            }
9386
        }
9387
9388
        if (is_array($links_to_create)) {
9389
            foreach ($links_to_create as $file => $link) {
9390
                $content = '<!DOCTYPE html><head>
9391
                            <meta charset="'.api_get_language_isocode().'" />
9392
                            <title>'.$link['title'].'</title>
9393
                            </head>
9394
                            <body dir="'.api_get_text_direction().'">
9395
                            <div style="text-align:center">
9396
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
9397
                            </body>
9398
                            </html>';
9399
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
9400
            }
9401
        }
9402
9403
        // Add non exportable message explanation.
9404
        $lang_not_exportable = get_lang('This learning object or activity is not SCORM compliant. That\'s why it is not exportable.');
9405
        $file_content = '<!DOCTYPE html><head>
9406
                        <meta charset="'.api_get_language_isocode().'" />
9407
                        <title>'.$lang_not_exportable.'</title>
9408
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
9409
                        </head>
9410
                        <body dir="'.api_get_text_direction().'">';
9411
        $file_content .=
9412
            <<<EOD
9413
                    <style>
9414
            .error-message {
9415
                font-family: arial, verdana, helvetica, sans-serif;
9416
                border-width: 1px;
9417
                border-style: solid;
9418
                left: 50%;
9419
                margin: 10px auto;
9420
                min-height: 30px;
9421
                padding: 5px;
9422
                right: 50%;
9423
                width: 500px;
9424
                background-color: #FFD1D1;
9425
                border-color: #FF0000;
9426
                color: #000;
9427
            }
9428
        </style>
9429
    <body>
9430
        <div class="error-message">
9431
            $lang_not_exportable
9432
        </div>
9433
    </body>
9434
</html>
9435
EOD;
9436
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
9437
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
9438
        }
9439
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
9440
9441
        // Add the extra files that go along with a SCORM package.
9442
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
9443
9444
        $fs = new Filesystem();
9445
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
9446
9447
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
9448
        $manifest = @$xmldoc->saveXML();
9449
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
9450
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
9451
        $zip_folder->add(
9452
            $archivePath.'/'.$temp_dir_short,
9453
            PCLZIP_OPT_REMOVE_PATH,
9454
            $archivePath.'/'.$temp_dir_short.'/'
9455
        );
9456
9457
        // Clean possible temporary files.
9458
        foreach ($files_cleanup as $file) {
9459
            $res = unlink($file);
9460
            if (false === $res) {
9461
                error_log(
9462
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
9463
                    0
9464
                );
9465
            }
9466
        }
9467
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
9468
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
9469
    }
9470
9471
    /**
9472
     * @param int $lp_id
9473
     *
9474
     * @return bool
9475
     */
9476
    public function scorm_export_to_pdf($lp_id)
9477
    {
9478
        $lp_id = (int) $lp_id;
9479
        $files_to_export = [];
9480
9481
        $sessionId = api_get_session_id();
9482
        $course_data = api_get_course_info($this->cc);
9483
9484
        if (!empty($course_data)) {
9485
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
9486
            $list = self::get_flat_ordered_items_list($lp_id);
9487
            if (!empty($list)) {
9488
                foreach ($list as $item_id) {
9489
                    $item = $this->items[$item_id];
9490
                    switch ($item->type) {
9491
                        case 'document':
9492
                            // Getting documents from a LP with chamilo documents
9493
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
9494
                            // Try loading document from the base course.
9495
                            if (empty($file_data) && !empty($sessionId)) {
9496
                                $file_data = DocumentManager::get_document_data_by_id(
9497
                                    $item->path,
9498
                                    $this->cc,
9499
                                    false,
9500
                                    0
9501
                                );
9502
                            }
9503
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
9504
                            if (file_exists($file_path)) {
9505
                                $files_to_export[] = [
9506
                                    'title' => $item->get_title(),
9507
                                    'path' => $file_path,
9508
                                ];
9509
                            }
9510
                            break;
9511
                        case 'asset': //commes from a scorm package generated by chamilo
9512
                        case 'sco':
9513
                            $file_path = $scorm_path.'/'.$item->path;
9514
                            if (file_exists($file_path)) {
9515
                                $files_to_export[] = [
9516
                                    'title' => $item->get_title(),
9517
                                    'path' => $file_path,
9518
                                ];
9519
                            }
9520
                            break;
9521
                        case 'dir':
9522
                            $files_to_export[] = [
9523
                                'title' => $item->get_title(),
9524
                                'path' => null,
9525
                            ];
9526
                            break;
9527
                    }
9528
                }
9529
            }
9530
9531
            $pdf = new PDF();
9532
            $result = $pdf->html_to_pdf(
9533
                $files_to_export,
9534
                $this->name,
9535
                $this->cc,
9536
                true,
9537
                true,
9538
                true,
9539
                $this->get_name()
9540
            );
9541
9542
            return $result;
9543
        }
9544
9545
        return false;
9546
    }
9547
9548
    /**
9549
     * Temp function to be moved in main_api or the best place around for this.
9550
     * Creates a file path if it doesn't exist.
9551
     *
9552
     * @param string $path
9553
     */
9554
    public function create_path($path)
9555
    {
9556
        $path_bits = explode('/', dirname($path));
9557
9558
        // IS_WINDOWS_OS has been defined in main_api.lib.php
9559
        $path_built = IS_WINDOWS_OS ? '' : '/';
9560
        foreach ($path_bits as $bit) {
9561
            if (!empty($bit)) {
9562
                $new_path = $path_built.$bit;
9563
                if (is_dir($new_path)) {
9564
                    $path_built = $new_path.'/';
9565
                } else {
9566
                    mkdir($new_path, api_get_permissions_for_new_directories());
9567
                    $path_built = $new_path.'/';
9568
                }
9569
            }
9570
        }
9571
    }
9572
9573
    /**
9574
     * @param int    $lp_id
9575
     * @param string $status
9576
     */
9577
    public function set_autolaunch($lp_id, $status)
9578
    {
9579
        $course_id = api_get_course_int_id();
9580
        $lp_id = (int) $lp_id;
9581
        $status = (int) $status;
9582
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
9583
9584
        // Setting everything to autolaunch = 0
9585
        $attributes['autolaunch'] = 0;
9586
        $where = [
9587
            'session_id = ? AND c_id = ? ' => [
9588
                api_get_session_id(),
9589
                $course_id,
9590
            ],
9591
        ];
9592
        Database::update($lp_table, $attributes, $where);
9593
        if (1 == $status) {
9594
            //Setting my lp_id to autolaunch = 1
9595
            $attributes['autolaunch'] = 1;
9596
            $where = [
9597
                'iid = ? AND session_id = ? AND c_id = ?' => [
9598
                    $lp_id,
9599
                    api_get_session_id(),
9600
                    $course_id,
9601
                ],
9602
            ];
9603
            Database::update($lp_table, $attributes, $where);
9604
        }
9605
    }
9606
9607
    /**
9608
     * Gets previous_item_id for the next element of the lp_item table.
9609
     *
9610
     * @author Isaac flores paz
9611
     *
9612
     * @return int Previous item ID
9613
     */
9614
    public function select_previous_item_id()
9615
    {
9616
        $course_id = api_get_course_int_id();
9617
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9618
9619
        // Get the max order of the items
9620
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
9621
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9622
        $rs_max_order = Database::query($sql);
9623
        $row_max_order = Database::fetch_object($rs_max_order);
9624
        $max_order = $row_max_order->display_order;
9625
        // Get the previous item ID
9626
        $sql = "SELECT iid as previous FROM $table_lp_item
9627
                WHERE
9628
                    c_id = $course_id AND
9629
                    lp_id = ".$this->lp_id." AND
9630
                    display_order = '$max_order' ";
9631
        $rs_max = Database::query($sql);
9632
        $row_max = Database::fetch_object($rs_max);
9633
9634
        // Return the previous item ID
9635
        return $row_max->previous;
9636
    }
9637
9638
    /**
9639
     * Copies an LP.
9640
     */
9641
    public function copy()
9642
    {
9643
        // Course builder
9644
        $cb = new CourseBuilder();
9645
9646
        //Setting tools that will be copied
9647
        $cb->set_tools_to_build(['learnpaths']);
9648
9649
        //Setting elements that will be copied
9650
        $cb->set_tools_specific_id_list(
9651
            ['learnpaths' => [$this->lp_id]]
9652
        );
9653
9654
        $course = $cb->build();
9655
9656
        //Course restorer
9657
        $course_restorer = new CourseRestorer($course);
9658
        $course_restorer->set_add_text_in_items(true);
9659
        $course_restorer->set_tool_copy_settings(
9660
            ['learnpaths' => ['reset_dates' => true]]
9661
        );
9662
        $course_restorer->restore(
9663
            api_get_course_id(),
9664
            api_get_session_id(),
9665
            false,
9666
            false
9667
        );
9668
    }
9669
9670
    /**
9671
     * Verify document size.
9672
     *
9673
     * @param string $s
9674
     *
9675
     * @return bool
9676
     */
9677
    public static function verify_document_size($s)
9678
    {
9679
        $post_max = ini_get('post_max_size');
9680
        if ('M' == substr($post_max, -1, 1)) {
9681
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
9682
        } elseif ('G' == substr($post_max, -1, 1)) {
9683
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
9684
        }
9685
        $upl_max = ini_get('upload_max_filesize');
9686
        if ('M' == substr($upl_max, -1, 1)) {
9687
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
9688
        } elseif ('G' == substr($upl_max, -1, 1)) {
9689
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
9690
        }
9691
9692
        $repo = Container::getDocumentRepository();
9693
        $documents_total_space = $repo->getTotalSpace(api_get_course_int_id());
9694
9695
        $course_max_space = DocumentManager::get_course_quota();
9696
        $total_size = filesize($s) + $documents_total_space;
9697
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
9698
            return true;
9699
        }
9700
9701
        return false;
9702
    }
9703
9704
    /**
9705
     * Clear LP prerequisites.
9706
     */
9707
    public function clear_prerequisites()
9708
    {
9709
        $course_id = $this->get_course_int_id();
9710
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9711
        $lp_id = $this->get_id();
9712
        // Cleaning prerequisites
9713
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
9714
                WHERE c_id = $course_id AND lp_id = $lp_id";
9715
        Database::query($sql);
9716
9717
        // Cleaning mastery score for exercises
9718
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
9719
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
9720
        Database::query($sql);
9721
    }
9722
9723
    public function set_previous_step_as_prerequisite_for_all_items()
9724
    {
9725
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9726
        $course_id = $this->get_course_int_id();
9727
        $lp_id = $this->get_id();
9728
9729
        if (!empty($this->items)) {
9730
            $previous_item_id = null;
9731
            $previous_item_max = 0;
9732
            $previous_item_type = null;
9733
            $last_item_not_dir = null;
9734
            $last_item_not_dir_type = null;
9735
            $last_item_not_dir_max = null;
9736
9737
            foreach ($this->ordered_items as $itemId) {
9738
                $item = $this->getItem($itemId);
9739
                // if there was a previous item... (otherwise jump to set it)
9740
                if (!empty($previous_item_id)) {
9741
                    $current_item_id = $item->get_id(); //save current id
9742
                    if ('dir' != $item->get_type()) {
9743
                        // Current item is not a folder, so it qualifies to get a prerequisites
9744
                        if ('quiz' == $last_item_not_dir_type) {
9745
                            // if previous is quiz, mark its max score as default score to be achieved
9746
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
9747
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
9748
                            Database::query($sql);
9749
                        }
9750
                        // now simply update the prerequisite to set it to the last non-chapter item
9751
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
9752
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
9753
                        Database::query($sql);
9754
                        // record item as 'non-chapter' reference
9755
                        $last_item_not_dir = $item->get_id();
9756
                        $last_item_not_dir_type = $item->get_type();
9757
                        $last_item_not_dir_max = $item->get_max();
9758
                    }
9759
                } else {
9760
                    if ('dir' != $item->get_type()) {
9761
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
9762
                        $last_item_not_dir = $item->get_id();
9763
                        $last_item_not_dir_type = $item->get_type();
9764
                        $last_item_not_dir_max = $item->get_max();
9765
                    }
9766
                }
9767
                // Saving the item as "previous item" for the next loop
9768
                $previous_item_id = $item->get_id();
9769
                $previous_item_max = $item->get_max();
9770
                $previous_item_type = $item->get_type();
9771
            }
9772
        }
9773
    }
9774
9775
    /**
9776
     * @param array $params
9777
     *
9778
     * @return int
9779
     */
9780
    public static function createCategory($params)
9781
    {
9782
        $courseEntity = api_get_course_entity(api_get_course_int_id());
9783
9784
        $item = new CLpCategory();
9785
        $item
9786
            ->setName($params['name'])
9787
            ->setCId($params['c_id'])
9788
            ->setParent($courseEntity)
9789
            ->addCourseLink($courseEntity, api_get_session_entity())
9790
        ;
9791
9792
        $repo = Container::getLpCategoryRepository();
9793
        $repo->create($item);
9794
9795
        /*api_item_property_update(
9796
            api_get_course_info(),
9797
            TOOL_LEARNPATH_CATEGORY,
9798
            $item->getId(),
9799
            'visible',
9800
            api_get_user_id()
9801
        );*/
9802
9803
        return $item->getIid();
9804
    }
9805
9806
    /**
9807
     * @param array $params
9808
     */
9809
    public static function updateCategory($params)
9810
    {
9811
        $em = Database::getManager();
9812
        /** @var CLpCategory $item */
9813
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
9814
        if ($item) {
9815
            $item->setName($params['name']);
9816
            $em->persist($item);
9817
            $em->flush();
9818
        }
9819
    }
9820
9821
    /**
9822
     * @param int $id
9823
     */
9824
    public static function moveUpCategory($id)
9825
    {
9826
        $id = (int) $id;
9827
        $em = Database::getManager();
9828
        /** @var CLpCategory $item */
9829
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
9830
        if ($item) {
9831
            $position = $item->getPosition() - 1;
9832
            $item->setPosition($position);
9833
            $em->persist($item);
9834
            $em->flush();
9835
        }
9836
    }
9837
9838
    /**
9839
     * @param int $id
9840
     */
9841
    public static function moveDownCategory($id)
9842
    {
9843
        $id = (int) $id;
9844
        $em = Database::getManager();
9845
        /** @var CLpCategory $item */
9846
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
9847
        if ($item) {
9848
            $position = $item->getPosition() + 1;
9849
            $item->setPosition($position);
9850
            $em->persist($item);
9851
            $em->flush();
9852
        }
9853
    }
9854
9855
    public static function getLpList($courseId)
9856
    {
9857
        $table = Database::get_course_table(TABLE_LP_MAIN);
9858
        $courseId = (int) $courseId;
9859
9860
        $sql = "SELECT * FROM $table WHERE c_id = $courseId";
9861
        $result = Database::query($sql);
9862
9863
        return Database::store_result($result, 'ASSOC');
9864
    }
9865
9866
    /**
9867
     * @param int $courseId
9868
     *
9869
     * @throws \Doctrine\ORM\Query\QueryException
9870
     *
9871
     * @return int|mixed
9872
     */
9873
    public static function getCountCategories($courseId)
9874
    {
9875
        if (empty($courseId)) {
9876
            return 0;
9877
        }
9878
        $em = Database::getManager();
9879
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
9880
        $query->setParameter('id', $courseId);
9881
9882
        return $query->getSingleScalarResult();
9883
    }
9884
9885
    /**
9886
     * @param int $courseId
9887
     *
9888
     * @return CLpCategory[]
9889
     */
9890
    public static function getCategories($courseId)
9891
    {
9892
        $em = Database::getManager();
9893
9894
        // Using doctrine extensions
9895
        /** @var SortableRepository $repo */
9896
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
9897
9898
        return $repo->getBySortableGroupsQuery(['cId' => $courseId])->getResult();
9899
    }
9900
9901
    public static function getCategorySessionId($id)
9902
    {
9903
        if (false === api_get_configuration_value('allow_session_lp_category')) {
9904
            return 0;
9905
        }
9906
9907
        $table = Database::get_course_table(TABLE_LP_CATEGORY);
9908
        $id = (int) $id;
9909
9910
        $sql = "SELECT session_id FROM $table WHERE iid = $id";
9911
        $result = Database::query($sql);
9912
        $result = Database::fetch_array($result, 'ASSOC');
9913
9914
        if ($result) {
9915
            return (int) $result['session_id'];
9916
        }
9917
9918
        return 0;
9919
    }
9920
9921
    /**
9922
     * @param int $id
9923
     *
9924
     * @return CLpCategory
9925
     */
9926
    public static function getCategory($id)
9927
    {
9928
        $id = (int) $id;
9929
        $em = Database::getManager();
9930
9931
        return $em->find('ChamiloCourseBundle:CLpCategory', $id);
9932
    }
9933
9934
    /**
9935
     * @param int $courseId
9936
     *
9937
     * @return array
9938
     */
9939
    public static function getCategoryByCourse($courseId)
9940
    {
9941
        $em = Database::getManager();
9942
        $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
9943
            ['cId' => $courseId]
9944
        );
9945
9946
        return $items;
9947
    }
9948
9949
    /**
9950
     * @param int $id
9951
     */
9952
    public static function deleteCategory($id): bool
9953
    {
9954
        $repo = Container::getLpCategoryRepository();
9955
        /** @var CLpCategory $category */
9956
        $category = $repo->find($id);
9957
        if ($category) {
9958
            $em = Database::getManager();
9959
            $lps = $category->getLps();
9960
9961
            foreach ($lps as $lp) {
9962
                $lp->setCategory(null);
9963
                $em->persist($lp);
9964
            }
9965
9966
            // Removing category.
9967
            $em->remove($category);
9968
            $em->flush();
9969
9970
            return true;
9971
        }
9972
9973
        return false;
9974
    }
9975
9976
    /**
9977
     * @param int  $courseId
9978
     * @param bool $addSelectOption
9979
     *
9980
     * @return mixed
9981
     */
9982
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
9983
    {
9984
        $items = self::getCategoryByCourse($courseId);
9985
        $cats = [];
9986
        if ($addSelectOption) {
9987
            $cats = [get_lang('Select a category')];
9988
        }
9989
9990
        if (!empty($items)) {
9991
            foreach ($items as $cat) {
9992
                $cats[$cat->getIid()] = $cat->getName();
9993
            }
9994
        }
9995
9996
        return $cats;
9997
    }
9998
9999
    /**
10000
     * @param string $courseCode
10001
     * @param int    $lpId
10002
     * @param int    $user_id
10003
     *
10004
     * @return learnpath
10005
     */
10006
    public static function getLpFromSession($courseCode, $lpId, $user_id)
10007
    {
10008
        $debug = 0;
10009
        $learnPath = null;
10010
        $lpObject = Session::read('lpobject');
10011
        if (null !== $lpObject) {
10012
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
10013
            if ($debug) {
10014
                error_log('getLpFromSession: unserialize');
10015
                error_log('------getLpFromSession------');
10016
                error_log('------unserialize------');
10017
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
10018
                error_log("api_get_sessionid: ".api_get_session_id());
10019
            }
10020
        }
10021
10022
        if (!is_object($learnPath)) {
10023
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
10024
            if ($debug) {
10025
                error_log('------getLpFromSession------');
10026
                error_log('getLpFromSession: create new learnpath');
10027
                error_log("create new LP with $courseCode - $lpId - $user_id");
10028
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
10029
                error_log("api_get_sessionid: ".api_get_session_id());
10030
            }
10031
        }
10032
10033
        return $learnPath;
10034
    }
10035
10036
    /**
10037
     * @param int $itemId
10038
     *
10039
     * @return learnpathItem|false
10040
     */
10041
    public function getItem($itemId)
10042
    {
10043
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
10044
            return $this->items[$itemId];
10045
        }
10046
10047
        return false;
10048
    }
10049
10050
    /**
10051
     * @return int
10052
     */
10053
    public function getCurrentAttempt()
10054
    {
10055
        $attempt = $this->getItem($this->get_current_item_id());
10056
        if ($attempt) {
10057
            return $attempt->get_attempt_id();
10058
        }
10059
10060
        return 0;
10061
    }
10062
10063
    /**
10064
     * @return int
10065
     */
10066
    public function getCategoryId()
10067
    {
10068
        return (int) $this->categoryId;
10069
    }
10070
10071
    /**
10072
     * Get whether this is a learning path with the possibility to subscribe
10073
     * users or not.
10074
     *
10075
     * @return int
10076
     */
10077
    public function getSubscribeUsers()
10078
    {
10079
        return $this->subscribeUsers;
10080
    }
10081
10082
    /**
10083
     * Calculate the count of stars for a user in this LP
10084
     * This calculation is based on the following rules:
10085
     * - the student gets one star when he gets to 50% of the learning path
10086
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
10087
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
10088
     * - the student gets the final star when the score for the *last* test is >= 80%.
10089
     *
10090
     * @param int $sessionId Optional. The session ID
10091
     *
10092
     * @return int The count of stars
10093
     */
10094
    public function getCalculateStars($sessionId = 0)
10095
    {
10096
        $stars = 0;
10097
        $progress = self::getProgress(
10098
            $this->lp_id,
10099
            $this->user_id,
10100
            $this->course_int_id,
10101
            $sessionId
10102
        );
10103
10104
        if ($progress >= 50) {
10105
            $stars++;
10106
        }
10107
10108
        // Calculate stars chapters evaluation
10109
        $exercisesItems = $this->getExercisesItems();
10110
10111
        if (!empty($exercisesItems)) {
10112
            $totalResult = 0;
10113
10114
            foreach ($exercisesItems as $exerciseItem) {
10115
                $exerciseResultInfo = Event::getExerciseResultsByUser(
10116
                    $this->user_id,
10117
                    $exerciseItem->path,
10118
                    $this->course_int_id,
10119
                    $sessionId,
10120
                    $this->lp_id,
10121
                    $exerciseItem->db_id
10122
                );
10123
10124
                $exerciseResultInfo = end($exerciseResultInfo);
10125
10126
                if (!$exerciseResultInfo) {
10127
                    continue;
10128
                }
10129
10130
                if (!empty($exerciseResultInfo['max_score'])) {
10131
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
10132
                } else {
10133
                    $exerciseResult = 0;
10134
                }
10135
                $totalResult += $exerciseResult;
10136
            }
10137
10138
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
10139
10140
            if ($totalExerciseAverage >= 50) {
10141
                $stars++;
10142
            }
10143
10144
            if ($totalExerciseAverage >= 80) {
10145
                $stars++;
10146
            }
10147
        }
10148
10149
        // Calculate star for final evaluation
10150
        $finalEvaluationItem = $this->getFinalEvaluationItem();
10151
10152
        if (!empty($finalEvaluationItem)) {
10153
            $evaluationResultInfo = Event::getExerciseResultsByUser(
10154
                $this->user_id,
10155
                $finalEvaluationItem->path,
10156
                $this->course_int_id,
10157
                $sessionId,
10158
                $this->lp_id,
10159
                $finalEvaluationItem->db_id
10160
            );
10161
10162
            $evaluationResultInfo = end($evaluationResultInfo);
10163
10164
            if ($evaluationResultInfo) {
10165
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
10166
10167
                if ($evaluationResult >= 80) {
10168
                    $stars++;
10169
                }
10170
            }
10171
        }
10172
10173
        return $stars;
10174
    }
10175
10176
    /**
10177
     * Get the items of exercise type.
10178
     *
10179
     * @return array The items. Otherwise return false
10180
     */
10181
    public function getExercisesItems()
10182
    {
10183
        $exercises = [];
10184
        foreach ($this->items as $item) {
10185
            if ('quiz' != $item->type) {
10186
                continue;
10187
            }
10188
            $exercises[] = $item;
10189
        }
10190
10191
        array_pop($exercises);
10192
10193
        return $exercises;
10194
    }
10195
10196
    /**
10197
     * Get the item of exercise type (evaluation type).
10198
     *
10199
     * @return array The final evaluation. Otherwise return false
10200
     */
10201
    public function getFinalEvaluationItem()
10202
    {
10203
        $exercises = [];
10204
        foreach ($this->items as $item) {
10205
            if (TOOL_QUIZ !== $item->type) {
10206
                continue;
10207
            }
10208
10209
            $exercises[] = $item;
10210
        }
10211
10212
        return array_pop($exercises);
10213
    }
10214
10215
    /**
10216
     * Calculate the total points achieved for the current user in this learning path.
10217
     *
10218
     * @param int $sessionId Optional. The session Id
10219
     *
10220
     * @return int
10221
     */
10222
    public function getCalculateScore($sessionId = 0)
10223
    {
10224
        // Calculate stars chapters evaluation
10225
        $exercisesItems = $this->getExercisesItems();
10226
        $finalEvaluationItem = $this->getFinalEvaluationItem();
10227
        $totalExercisesResult = 0;
10228
        $totalEvaluationResult = 0;
10229
10230
        if (false !== $exercisesItems) {
10231
            foreach ($exercisesItems as $exerciseItem) {
10232
                $exerciseResultInfo = Event::getExerciseResultsByUser(
10233
                    $this->user_id,
10234
                    $exerciseItem->path,
10235
                    $this->course_int_id,
10236
                    $sessionId,
10237
                    $this->lp_id,
10238
                    $exerciseItem->db_id
10239
                );
10240
10241
                $exerciseResultInfo = end($exerciseResultInfo);
10242
10243
                if (!$exerciseResultInfo) {
10244
                    continue;
10245
                }
10246
10247
                $totalExercisesResult += $exerciseResultInfo['score'];
10248
            }
10249
        }
10250
10251
        if (!empty($finalEvaluationItem)) {
10252
            $evaluationResultInfo = Event::getExerciseResultsByUser(
10253
                $this->user_id,
10254
                $finalEvaluationItem->path,
10255
                $this->course_int_id,
10256
                $sessionId,
10257
                $this->lp_id,
10258
                $finalEvaluationItem->db_id
10259
            );
10260
10261
            $evaluationResultInfo = end($evaluationResultInfo);
10262
10263
            if ($evaluationResultInfo) {
10264
                $totalEvaluationResult += $evaluationResultInfo['score'];
10265
            }
10266
        }
10267
10268
        return $totalExercisesResult + $totalEvaluationResult;
10269
    }
10270
10271
    /**
10272
     * Check if URL is not allowed to be show in a iframe.
10273
     *
10274
     * @param string $src
10275
     *
10276
     * @return string
10277
     */
10278
    public function fixBlockedLinks($src)
10279
    {
10280
        $urlInfo = parse_url($src);
10281
10282
        $platformProtocol = 'https';
10283
        if (false === strpos(api_get_path(WEB_CODE_PATH), 'https')) {
10284
            $platformProtocol = 'http';
10285
        }
10286
10287
        $protocolFixApplied = false;
10288
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
10289
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
10290
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
10291
10292
        if ($platformProtocol != $scheme) {
10293
            Session::write('x_frame_source', $src);
10294
            $src = 'blank.php?error=x_frames_options';
10295
            $protocolFixApplied = true;
10296
        }
10297
10298
        if (false == $protocolFixApplied) {
10299
            if (false === strpos(api_get_path(WEB_PATH), $host)) {
10300
                // Check X-Frame-Options
10301
                $ch = curl_init();
10302
                $options = [
10303
                    CURLOPT_URL => $src,
10304
                    CURLOPT_RETURNTRANSFER => true,
10305
                    CURLOPT_HEADER => true,
10306
                    CURLOPT_FOLLOWLOCATION => true,
10307
                    CURLOPT_ENCODING => "",
10308
                    CURLOPT_AUTOREFERER => true,
10309
                    CURLOPT_CONNECTTIMEOUT => 120,
10310
                    CURLOPT_TIMEOUT => 120,
10311
                    CURLOPT_MAXREDIRS => 10,
10312
                ];
10313
10314
                $proxySettings = api_get_configuration_value('proxy_settings');
10315
                if (!empty($proxySettings) &&
10316
                    isset($proxySettings['curl_setopt_array'])
10317
                ) {
10318
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
10319
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
10320
                }
10321
10322
                curl_setopt_array($ch, $options);
10323
                $response = curl_exec($ch);
10324
                $httpCode = curl_getinfo($ch);
10325
                $headers = substr($response, 0, $httpCode['header_size']);
10326
10327
                $error = false;
10328
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
10329
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
10330
                ) {
10331
                    $error = true;
10332
                }
10333
10334
                if ($error) {
10335
                    Session::write('x_frame_source', $src);
10336
                    $src = 'blank.php?error=x_frames_options';
10337
                }
10338
            }
10339
        }
10340
10341
        return $src;
10342
    }
10343
10344
    /**
10345
     * Check if this LP has a created forum in the basis course.
10346
     *
10347
     * @return bool
10348
     */
10349
    public function lpHasForum()
10350
    {
10351
        $forumTable = Database::get_course_table(TABLE_FORUM);
10352
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
10353
10354
        $fakeFrom = "
10355
            $forumTable f
10356
            INNER JOIN $itemProperty ip
10357
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
10358
        ";
10359
10360
        $resultData = Database::select(
10361
            'COUNT(f.iid) AS qty',
10362
            $fakeFrom,
10363
            [
10364
                'where' => [
10365
                    'ip.visibility != ? AND ' => 2,
10366
                    'ip.tool = ? AND ' => TOOL_FORUM,
10367
                    'f.c_id = ? AND ' => intval($this->course_int_id),
10368
                    'f.lp_id = ?' => intval($this->lp_id),
10369
                ],
10370
            ],
10371
            'first'
10372
        );
10373
10374
        return $resultData['qty'] > 0;
10375
    }
10376
10377
    /**
10378
     * Get the forum for this learning path.
10379
     *
10380
     * @param int $sessionId
10381
     *
10382
     * @return array
10383
     */
10384
    public function getForum($sessionId = 0)
10385
    {
10386
        $repo = Container::getForumRepository();
10387
10388
        $course = api_get_course_entity();
10389
        $session = api_get_session_entity($sessionId);
10390
        $qb = $repo->getResourcesByCourse($course, $session);
10391
10392
        return $qb->getQuery()->getResult();
10393
    }
10394
10395
    /**
10396
     * Create a forum for this learning path.
10397
     *
10398
     * @return int The forum ID if was created. Otherwise return false
10399
     */
10400
    public function createForum(CForumCategory $forumCategory)
10401
    {
10402
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
10403
10404
        return store_forum(
10405
            [
10406
                'lp_id' => $this->lp_id,
10407
                'forum_title' => $this->name,
10408
                'forum_comment' => null,
10409
                'forum_category' => $forumCategory->getIid(),
10410
                'students_can_edit_group' => ['students_can_edit' => 0],
10411
                'allow_new_threads_group' => ['allow_new_threads' => 0],
10412
                'default_view_type_group' => ['default_view_type' => 'flat'],
10413
                'group_forum' => 0,
10414
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
10415
            ],
10416
            [],
10417
            true
10418
        );
10419
    }
10420
10421
    /**
10422
     * Get the LP Final Item form.
10423
     *
10424
     * @throws Exception
10425
     * @throws HTML_QuickForm_Error
10426
     *
10427
     * @return string
10428
     */
10429
    public function getFinalItemForm()
10430
    {
10431
        $finalItem = $this->getFinalItem();
10432
        $title = '';
10433
10434
        if ($finalItem) {
10435
            $title = $finalItem->get_title();
10436
            $buttonText = get_lang('Save');
10437
            $content = $this->getSavedFinalItem();
10438
        } else {
10439
            $buttonText = get_lang('Add this document to the course');
10440
            $content = $this->getFinalItemTemplate();
10441
        }
10442
10443
        $editorConfig = [
10444
            'ToolbarSet' => 'LearningPathDocuments',
10445
            'Width' => '100%',
10446
            'Height' => '500',
10447
            'FullPage' => true,
10448
//            'CreateDocumentDir' => $relative_prefix,
10449
    //            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
10450
  //          'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
10451
        ];
10452
10453
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
10454
            'type' => 'document',
10455
            'lp_id' => $this->lp_id,
10456
        ]);
10457
10458
        $form = new FormValidator('final_item', 'POST', $url);
10459
        $form->addText('title', get_lang('Title'));
10460
        $form->addButtonSave($buttonText);
10461
        $form->addHtml(
10462
            Display::return_message(
10463
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
10464
                'normal',
10465
                false
10466
            )
10467
        );
10468
10469
        $renderer = $form->defaultRenderer();
10470
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
10471
10472
        $form->addHtmlEditor(
10473
            'content_lp_certificate',
10474
            null,
10475
            true,
10476
            false,
10477
            $editorConfig,
10478
            true
10479
        );
10480
        $form->addHidden('action', 'add_final_item');
10481
        $form->addHidden('path', Session::read('pathItem'));
10482
        $form->addHidden('previous', $this->get_last());
10483
        $form->setDefaults(
10484
            ['title' => $title, 'content_lp_certificate' => $content]
10485
        );
10486
10487
        if ($form->validate()) {
10488
            $values = $form->exportValues();
10489
            $lastItemId = $this->getLastInFirstLevel();
10490
10491
            if (!$finalItem) {
10492
                $documentId = $this->create_document(
10493
                    $this->course_info,
10494
                    $values['content_lp_certificate'],
10495
                    $values['title']
10496
                );
10497
                $this->add_item(
10498
                    0,
10499
                    $lastItemId,
10500
                    'final_item',
10501
                    $documentId,
10502
                    $values['title'],
10503
                    ''
10504
                );
10505
10506
                Display::addFlash(
10507
                    Display::return_message(get_lang('Added'))
10508
                );
10509
            } else {
10510
                $this->edit_document($this->course_info);
10511
            }
10512
        }
10513
10514
        return $form->returnForm();
10515
    }
10516
10517
    /**
10518
     * Check if the current lp item is first, both, last or none from lp list.
10519
     *
10520
     * @param int $currentItemId
10521
     *
10522
     * @return string
10523
     */
10524
    public function isFirstOrLastItem($currentItemId)
10525
    {
10526
        $lpItemId = [];
10527
        $typeListNotToVerify = self::getChapterTypes();
10528
10529
        // Using get_toc() function instead $this->items because returns the correct order of the items
10530
        foreach ($this->get_toc() as $item) {
10531
            if (!in_array($item['type'], $typeListNotToVerify)) {
10532
                $lpItemId[] = $item['id'];
10533
            }
10534
        }
10535
10536
        $lastLpItemIndex = count($lpItemId) - 1;
10537
        $position = array_search($currentItemId, $lpItemId);
10538
10539
        switch ($position) {
10540
            case 0:
10541
                if (!$lastLpItemIndex) {
10542
                    $answer = 'both';
10543
                    break;
10544
                }
10545
10546
                $answer = 'first';
10547
                break;
10548
            case $lastLpItemIndex:
10549
                $answer = 'last';
10550
                break;
10551
            default:
10552
                $answer = 'none';
10553
        }
10554
10555
        return $answer;
10556
    }
10557
10558
    /**
10559
     * Get whether this is a learning path with the accumulated SCORM time or not.
10560
     *
10561
     * @return int
10562
     */
10563
    public function getAccumulateScormTime()
10564
    {
10565
        return $this->accumulateScormTime;
10566
    }
10567
10568
    /**
10569
     * Returns an HTML-formatted link to a resource, to incorporate directly into
10570
     * the new learning path tool.
10571
     *
10572
     * The function is a big switch on tool type.
10573
     * In each case, we query the corresponding table for information and build the link
10574
     * with that information.
10575
     *
10576
     * @author Yannick Warnier <[email protected]> - rebranding based on
10577
     * previous work (display_addedresource_link_in_learnpath())
10578
     *
10579
     * @param int $course_id      Course code
10580
     * @param int $learningPathId The learning path ID (in lp table)
10581
     * @param int $id_in_path     the unique index in the items table
10582
     * @param int $lpViewId
10583
     *
10584
     * @return string
10585
     */
10586
    public static function rl_get_resource_link_for_learnpath(
10587
        $course_id,
10588
        $learningPathId,
10589
        $id_in_path,
10590
        $lpViewId
10591
    ) {
10592
        $session_id = api_get_session_id();
10593
10594
        $learningPathId = (int) $learningPathId;
10595
        $id_in_path = (int) $id_in_path;
10596
        $lpViewId = (int) $lpViewId;
10597
10598
        $em = Database::getManager();
10599
        $lpItemRepo = $em->getRepository(CLpItem::class);
10600
10601
        /** @var CLpItem $rowItem */
10602
        $rowItem = $lpItemRepo->findOneBy([
10603
            'cId' => $course_id,
10604
            'lp' => $learningPathId,
10605
            'iid' => $id_in_path,
10606
        ]);
10607
10608
        if (!$rowItem) {
10609
            // Try one more time with "id"
10610
            /** @var CLpItem $rowItem */
10611
            $rowItem = $lpItemRepo->findOneBy([
10612
                'cId' => $course_id,
10613
                'lp' => $learningPathId,
10614
                'id' => $id_in_path,
10615
            ]);
10616
10617
            if (!$rowItem) {
10618
                return -1;
10619
            }
10620
        }
10621
10622
        $type = $rowItem->getItemType();
10623
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
10624
        $main_dir_path = api_get_path(WEB_CODE_PATH);
10625
        $link = '';
10626
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
10627
10628
        switch ($type) {
10629
            case 'dir':
10630
                return $main_dir_path.'lp/blank.php';
10631
            case TOOL_CALENDAR_EVENT:
10632
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
10633
            case TOOL_ANNOUNCEMENT:
10634
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
10635
            case TOOL_LINK:
10636
                $linkInfo = Link::getLinkInfo($id);
10637
                if (isset($linkInfo['url'])) {
10638
                    return $linkInfo['url'];
10639
                }
10640
10641
                return '';
10642
            case TOOL_QUIZ:
10643
                if (empty($id)) {
10644
                    return '';
10645
                }
10646
10647
                // Get the lp_item_view with the highest view_count.
10648
                $learnpathItemViewResult = $em
10649
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
10650
                    ->findBy(
10651
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
10652
                        ['viewCount' => 'DESC'],
10653
                        1
10654
                    );
10655
                /** @var CLpItemView $learnpathItemViewData */
10656
                $learnpathItemViewData = current($learnpathItemViewResult);
10657
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
10658
10659
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
10660
                    .http_build_query([
10661
                        'lp_init' => 1,
10662
                        'learnpath_item_view_id' => $learnpathItemViewId,
10663
                        'learnpath_id' => $learningPathId,
10664
                        'learnpath_item_id' => $id_in_path,
10665
                        'exerciseId' => $id,
10666
                    ]);
10667
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
10668
                /*$TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
10669
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
10670
                $myrow = Database::fetch_array($result);
10671
                $path = $myrow['path'];
10672
10673
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
10674
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
10675
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;*/
10676
            case TOOL_FORUM:
10677
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
10678
            case TOOL_THREAD:
10679
                // forum post
10680
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
10681
                if (empty($id)) {
10682
                    return '';
10683
                }
10684
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
10685
                $result = Database::query($sql);
10686
                $myrow = Database::fetch_array($result);
10687
10688
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
10689
                    .$extraParams;
10690
            case TOOL_POST:
10691
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
10692
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
10693
                $myrow = Database::fetch_array($result);
10694
10695
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
10696
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
10697
            case TOOL_READOUT_TEXT:
10698
                return api_get_path(WEB_CODE_PATH).
10699
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
10700
            case TOOL_DOCUMENT:
10701
                $repo = Container::getDocumentRepository();
10702
                $document = $repo->find($rowItem->getPath());
10703
                $file = $repo->getResourceFileUrl($document, [], UrlGeneratorInterface::ABSOLUTE_URL);
10704
10705
                return $file;
10706
10707
                $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...
10708
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v'];
10709
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
10710
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
10711
10712
                $openmethod = 2;
10713
                $officedoc = false;
10714
                Session::write('openmethod', $openmethod);
10715
                Session::write('officedoc', $officedoc);
10716
10717
                if ($showDirectUrl) {
10718
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
10719
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
10720
                        if (Link::isPdfLink($file)) {
10721
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
10722
10723
                            return $pdfUrl;
10724
                        }
10725
                    }
10726
10727
                    return $file;
10728
                }
10729
10730
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
10731
            case TOOL_LP_FINAL_ITEM:
10732
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
10733
                    .$extraParams;
10734
            case 'assignments':
10735
                return $main_dir_path.'work/work.php?'.$extraParams;
10736
            case TOOL_DROPBOX:
10737
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
10738
            case 'introduction_text': //DEPRECATED
10739
                return '';
10740
            case TOOL_COURSE_DESCRIPTION:
10741
                return $main_dir_path.'course_description?'.$extraParams;
10742
            case TOOL_GROUP:
10743
                return $main_dir_path.'group/group.php?'.$extraParams;
10744
            case TOOL_USER:
10745
                return $main_dir_path.'user/user.php?'.$extraParams;
10746
            case TOOL_STUDENTPUBLICATION:
10747
                if (!empty($rowItem->getPath())) {
10748
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
10749
                }
10750
10751
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
10752
        }
10753
10754
        return $link;
10755
    }
10756
10757
    /**
10758
     * Gets the name of a resource (generally used in learnpath when no name is provided).
10759
     *
10760
     * @author Yannick Warnier <[email protected]>
10761
     *
10762
     * @param string $course_code    Course code
10763
     * @param int    $learningPathId
10764
     * @param int    $id_in_path     The resource ID
10765
     *
10766
     * @return string
10767
     */
10768
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
10769
    {
10770
        $_course = api_get_course_info($course_code);
10771
        if (empty($_course)) {
10772
            return '';
10773
        }
10774
        $course_id = $_course['real_id'];
10775
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10776
        $learningPathId = (int) $learningPathId;
10777
        $id_in_path = (int) $id_in_path;
10778
10779
        $sql = "SELECT item_type, title, ref
10780
                FROM $tbl_lp_item
10781
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
10782
        $res_item = Database::query($sql);
10783
10784
        if (Database::num_rows($res_item) < 1) {
10785
            return '';
10786
        }
10787
        $row_item = Database::fetch_array($res_item);
10788
        $type = strtolower($row_item['item_type']);
10789
        $id = $row_item['ref'];
10790
        $output = '';
10791
10792
        switch ($type) {
10793
            case TOOL_CALENDAR_EVENT:
10794
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
10795
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
10796
                $myrow = Database::fetch_array($result);
10797
                $output = $myrow['title'];
10798
                break;
10799
            case TOOL_ANNOUNCEMENT:
10800
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
10801
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
10802
                $myrow = Database::fetch_array($result);
10803
                $output = $myrow['title'];
10804
                break;
10805
            case TOOL_LINK:
10806
                // Doesn't take $target into account.
10807
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
10808
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
10809
                $myrow = Database::fetch_array($result);
10810
                $output = $myrow['title'];
10811
                break;
10812
            case TOOL_QUIZ:
10813
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
10814
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
10815
                $myrow = Database::fetch_array($result);
10816
                $output = $myrow['title'];
10817
                break;
10818
            case TOOL_FORUM:
10819
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
10820
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
10821
                $myrow = Database::fetch_array($result);
10822
                $output = $myrow['forum_name'];
10823
                break;
10824
            case TOOL_THREAD:
10825
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
10826
                // Grabbing the title of the post.
10827
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
10828
                $result_title = Database::query($sql_title);
10829
                $myrow_title = Database::fetch_array($result_title);
10830
                $output = $myrow_title['post_title'];
10831
                break;
10832
            case TOOL_POST:
10833
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
10834
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
10835
                $result = Database::query($sql);
10836
                $post = Database::fetch_array($result);
10837
                $output = $post['post_title'];
10838
                break;
10839
            case 'dir':
10840
            case TOOL_DOCUMENT:
10841
                $title = $row_item['title'];
10842
                $output = '-';
10843
                if (!empty($title)) {
10844
                    $output = $title;
10845
                }
10846
                break;
10847
            case 'hotpotatoes':
10848
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
10849
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
10850
                $myrow = Database::fetch_array($result);
10851
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
10852
                $last = count($pathname) - 1; // Making a correct name for the link.
10853
                $filename = $pathname[$last]; // Making a correct name for the link.
10854
                $myrow['path'] = rawurlencode($myrow['path']);
10855
                $output = $filename;
10856
                break;
10857
        }
10858
10859
        return stripslashes($output);
10860
    }
10861
10862
    /**
10863
     * Get the parent names for the current item.
10864
     *
10865
     * @param int $newItemId Optional. The item ID
10866
     *
10867
     * @return array
10868
     */
10869
    public function getCurrentItemParentNames($newItemId = 0)
10870
    {
10871
        $newItemId = $newItemId ?: $this->get_current_item_id();
10872
        $return = [];
10873
        $item = $this->getItem($newItemId);
10874
        $parent = $this->getItem($item->get_parent());
10875
10876
        while ($parent) {
10877
            $return[] = $parent->get_title();
10878
            $parent = $this->getItem($parent->get_parent());
10879
        }
10880
10881
        return array_reverse($return);
10882
    }
10883
10884
    /**
10885
     * Reads and process "lp_subscription_settings" setting.
10886
     *
10887
     * @return array
10888
     */
10889
    public static function getSubscriptionSettings()
10890
    {
10891
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
10892
        if (empty($subscriptionSettings)) {
10893
            // By default allow both settings
10894
            $subscriptionSettings = [
10895
                'allow_add_users_to_lp' => true,
10896
                'allow_add_users_to_lp_category' => true,
10897
            ];
10898
        } else {
10899
            $subscriptionSettings = $subscriptionSettings['options'];
10900
        }
10901
10902
        return $subscriptionSettings;
10903
    }
10904
10905
    /**
10906
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
10907
     */
10908
    public function exportToCourseBuildFormat()
10909
    {
10910
        if (!api_is_allowed_to_edit()) {
10911
            return false;
10912
        }
10913
10914
        $courseBuilder = new CourseBuilder();
10915
        $itemList = [];
10916
        /** @var learnpathItem $item */
10917
        foreach ($this->items as $item) {
10918
            $itemList[$item->get_type()][] = $item->get_path();
10919
        }
10920
10921
        if (empty($itemList)) {
10922
            return false;
10923
        }
10924
10925
        if (isset($itemList['document'])) {
10926
            // Get parents
10927
            foreach ($itemList['document'] as $documentId) {
10928
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
10929
                if (!empty($documentInfo['parents'])) {
10930
                    foreach ($documentInfo['parents'] as $parentInfo) {
10931
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
10932
                            continue;
10933
                        }
10934
                        $itemList['document'][] = $parentInfo['iid'];
10935
                    }
10936
                }
10937
            }
10938
10939
            $courseInfo = api_get_course_info();
10940
            foreach ($itemList['document'] as $documentId) {
10941
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
10942
                $items = DocumentManager::get_resources_from_source_html(
10943
                    $documentInfo['absolute_path'],
10944
                    true,
10945
                    TOOL_DOCUMENT
10946
                );
10947
10948
                if (!empty($items)) {
10949
                    foreach ($items as $item) {
10950
                        // Get information about source url
10951
                        $url = $item[0]; // url
10952
                        $scope = $item[1]; // scope (local, remote)
10953
                        $type = $item[2]; // type (rel, abs, url)
10954
10955
                        $origParseUrl = parse_url($url);
10956
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
10957
10958
                        if ('local' == $scope) {
10959
                            if ('abs' == $type || 'rel' == $type) {
10960
                                $documentFile = strstr($realOrigPath, 'document');
10961
                                if (false !== strpos($realOrigPath, $documentFile)) {
10962
                                    $documentFile = str_replace('document', '', $documentFile);
10963
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
10964
                                    // Document found! Add it to the list
10965
                                    if ($itemDocumentId) {
10966
                                        $itemList['document'][] = $itemDocumentId;
10967
                                    }
10968
                                }
10969
                            }
10970
                        }
10971
                    }
10972
                }
10973
            }
10974
10975
            $courseBuilder->build_documents(
10976
                api_get_session_id(),
10977
                $this->get_course_int_id(),
10978
                true,
10979
                $itemList['document']
10980
            );
10981
        }
10982
10983
        if (isset($itemList['quiz'])) {
10984
            $courseBuilder->build_quizzes(
10985
                api_get_session_id(),
10986
                $this->get_course_int_id(),
10987
                true,
10988
                $itemList['quiz']
10989
            );
10990
        }
10991
10992
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
10993
10994
        /*if (!empty($itemList['thread'])) {
10995
            $postList = [];
10996
            foreach ($itemList['thread'] as $postId) {
10997
                $post = get_post_information($postId);
10998
                if ($post) {
10999
                    if (!isset($itemList['forum'])) {
11000
                        $itemList['forum'] = [];
11001
                    }
11002
                    $itemList['forum'][] = $post['forum_id'];
11003
                    $postList[] = $postId;
11004
                }
11005
            }
11006
11007
            if (!empty($postList)) {
11008
                $courseBuilder->build_forum_posts(
11009
                    $this->get_course_int_id(),
11010
                    null,
11011
                    null,
11012
                    $postList
11013
                );
11014
            }
11015
        }*/
11016
11017
        if (!empty($itemList['thread'])) {
11018
            $threadList = [];
11019
            $em = Database::getManager();
11020
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
11021
            foreach ($itemList['thread'] as $threadId) {
11022
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
11023
                $thread = $repo->find($threadId);
11024
                if ($thread) {
11025
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
11026
                    $threadList[] = $thread->getIid();
11027
                }
11028
            }
11029
11030
            if (!empty($threadList)) {
11031
                $courseBuilder->build_forum_topics(
11032
                    api_get_session_id(),
11033
                    $this->get_course_int_id(),
11034
                    null,
11035
                    $threadList
11036
                );
11037
            }
11038
        }
11039
11040
        $forumCategoryList = [];
11041
        if (isset($itemList['forum'])) {
11042
            foreach ($itemList['forum'] as $forumId) {
11043
                $forumInfo = get_forums($forumId);
11044
                $forumCategoryList[] = $forumInfo['forum_category'];
11045
            }
11046
        }
11047
11048
        if (!empty($forumCategoryList)) {
11049
            $courseBuilder->build_forum_category(
11050
                api_get_session_id(),
11051
                $this->get_course_int_id(),
11052
                true,
11053
                $forumCategoryList
11054
            );
11055
        }
11056
11057
        if (!empty($itemList['forum'])) {
11058
            $courseBuilder->build_forums(
11059
                api_get_session_id(),
11060
                $this->get_course_int_id(),
11061
                true,
11062
                $itemList['forum']
11063
            );
11064
        }
11065
11066
        if (isset($itemList['link'])) {
11067
            $courseBuilder->build_links(
11068
                api_get_session_id(),
11069
                $this->get_course_int_id(),
11070
                true,
11071
                $itemList['link']
11072
            );
11073
        }
11074
11075
        if (!empty($itemList['student_publication'])) {
11076
            $courseBuilder->build_works(
11077
                api_get_session_id(),
11078
                $this->get_course_int_id(),
11079
                true,
11080
                $itemList['student_publication']
11081
            );
11082
        }
11083
11084
        $courseBuilder->build_learnpaths(
11085
            api_get_session_id(),
11086
            $this->get_course_int_id(),
11087
            true,
11088
            [$this->get_id()],
11089
            false
11090
        );
11091
11092
        $courseBuilder->restoreDocumentsFromList();
11093
11094
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
11095
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
11096
        $result = DocumentManager::file_send_for_download(
11097
            $zipPath,
11098
            true,
11099
            $this->get_name().'.zip'
11100
        );
11101
11102
        if ($result) {
11103
            api_not_allowed();
11104
        }
11105
11106
        return true;
11107
    }
11108
11109
    /**
11110
     * Get whether this is a learning path with the accumulated work time or not.
11111
     *
11112
     * @return int
11113
     */
11114
    public function getAccumulateWorkTime()
11115
    {
11116
        return (int) $this->accumulateWorkTime;
11117
    }
11118
11119
    /**
11120
     * Get whether this is a learning path with the accumulated work time or not.
11121
     *
11122
     * @return int
11123
     */
11124
    public function getAccumulateWorkTimeTotalCourse()
11125
    {
11126
        $table = Database::get_course_table(TABLE_LP_MAIN);
11127
        $sql = "SELECT SUM(accumulate_work_time) AS total
11128
                FROM $table
11129
                WHERE c_id = ".$this->course_int_id;
11130
        $result = Database::query($sql);
11131
        $row = Database::fetch_array($result);
11132
11133
        return (int) $row['total'];
11134
    }
11135
11136
    /**
11137
     * @param int $lpId
11138
     * @param int $courseId
11139
     *
11140
     * @return mixed
11141
     */
11142
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
11143
    {
11144
        $lpId = (int) $lpId;
11145
        $courseId = (int) $courseId;
11146
11147
        $table = Database::get_course_table(TABLE_LP_MAIN);
11148
        $sql = "SELECT accumulate_work_time
11149
                FROM $table
11150
                WHERE c_id = $courseId AND id = $lpId";
11151
        $result = Database::query($sql);
11152
        $row = Database::fetch_array($result);
11153
11154
        return $row['accumulate_work_time'];
11155
    }
11156
11157
    /**
11158
     * @param int $courseId
11159
     *
11160
     * @return int
11161
     */
11162
    public static function getAccumulateWorkTimeTotal($courseId)
11163
    {
11164
        $table = Database::get_course_table(TABLE_LP_MAIN);
11165
        $courseId = (int) $courseId;
11166
        $sql = "SELECT SUM(accumulate_work_time) AS total
11167
                FROM $table
11168
                WHERE c_id = $courseId";
11169
        $result = Database::query($sql);
11170
        $row = Database::fetch_array($result);
11171
11172
        return (int) $row['total'];
11173
    }
11174
11175
    /**
11176
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
11177
     * and put the images in.
11178
     *
11179
     * @return array
11180
     */
11181
    public static function getIconSelect()
11182
    {
11183
        $theme = api_get_visual_theme();
11184
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
11185
        $icons = ['' => get_lang('Please select an option')];
11186
11187
        if (is_dir($path)) {
11188
            $finder = new Finder();
11189
            $finder->files()->in($path);
11190
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
11191
            /** @var SplFileInfo $file */
11192
            foreach ($finder as $file) {
11193
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
11194
                    $icons[$file->getFilename()] = $file->getFilename();
11195
                }
11196
            }
11197
        }
11198
11199
        return $icons;
11200
    }
11201
11202
    /**
11203
     * @param int $lpId
11204
     *
11205
     * @return string
11206
     */
11207
    public static function getSelectedIcon($lpId)
11208
    {
11209
        $extraFieldValue = new ExtraFieldValue('lp');
11210
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
11211
        $icon = '';
11212
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
11213
            $icon = $lpIcon['value'];
11214
        }
11215
11216
        return $icon;
11217
    }
11218
11219
    /**
11220
     * @param int $lpId
11221
     *
11222
     * @return string
11223
     */
11224
    public static function getSelectedIconHtml($lpId)
11225
    {
11226
        $icon = self::getSelectedIcon($lpId);
11227
11228
        if (empty($icon)) {
11229
            return '';
11230
        }
11231
11232
        $theme = api_get_visual_theme();
11233
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
11234
11235
        return Display::img($path);
11236
    }
11237
11238
    /**
11239
     * @param string $value
11240
     *
11241
     * @return string
11242
     */
11243
    public function cleanItemTitle($value)
11244
    {
11245
        $value = Security::remove_XSS(strip_tags($value));
11246
11247
        return $value;
11248
    }
11249
11250
    public function setItemTitle(FormValidator $form)
11251
    {
11252
        if (api_get_configuration_value('save_titles_as_html')) {
11253
            $form->addHtmlEditor(
11254
                'title',
11255
                get_lang('Title'),
11256
                true,
11257
                false,
11258
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
11259
            );
11260
        } else {
11261
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
11262
            $form->applyFilter('title', 'trim');
11263
            $form->applyFilter('title', 'html_filter');
11264
        }
11265
    }
11266
11267
    /**
11268
     * @return array
11269
     */
11270
    public function getItemsForForm($addParentCondition = false)
11271
    {
11272
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11273
        $course_id = api_get_course_int_id();
11274
11275
        $sql = "SELECT * FROM $tbl_lp_item
11276
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
11277
11278
        if ($addParentCondition) {
11279
            $sql .= ' AND parent_item_id = 0 ';
11280
        }
11281
        $sql .= ' ORDER BY display_order ASC';
11282
11283
        $result = Database::query($sql);
11284
        $arrLP = [];
11285
        while ($row = Database::fetch_array($result)) {
11286
            $arrLP[] = [
11287
                'iid' => $row['iid'],
11288
                'id' => $row['iid'],
11289
                'item_type' => $row['item_type'],
11290
                'title' => $this->cleanItemTitle($row['title']),
11291
                'title_raw' => $row['title'],
11292
                'path' => $row['path'],
11293
                'description' => Security::remove_XSS($row['description']),
11294
                'parent_item_id' => $row['parent_item_id'],
11295
                'previous_item_id' => $row['previous_item_id'],
11296
                'next_item_id' => $row['next_item_id'],
11297
                'display_order' => $row['display_order'],
11298
                'max_score' => $row['max_score'],
11299
                'min_score' => $row['min_score'],
11300
                'mastery_score' => $row['mastery_score'],
11301
                'prerequisite' => $row['prerequisite'],
11302
                'max_time_allowed' => $row['max_time_allowed'],
11303
                'prerequisite_min_score' => $row['prerequisite_min_score'],
11304
                'prerequisite_max_score' => $row['prerequisite_max_score'],
11305
            ];
11306
        }
11307
11308
        return $arrLP;
11309
    }
11310
11311
    /**
11312
     * Gets whether this SCORM learning path has been marked to use the score
11313
     * as progress. Takes into account whether the learnpath matches (SCORM
11314
     * content + less than 2 items).
11315
     *
11316
     * @return bool True if the score should be used as progress, false otherwise
11317
     */
11318
    public function getUseScoreAsProgress()
11319
    {
11320
        // If not a SCORM, we don't care about the setting
11321
        if (2 != $this->get_type()) {
11322
            return false;
11323
        }
11324
        // If more than one step in the SCORM, we don't care about the setting
11325
        if ($this->get_total_items_count() > 1) {
11326
            return false;
11327
        }
11328
        $extraFieldValue = new ExtraFieldValue('lp');
11329
        $doUseScore = false;
11330
        $useScore = $extraFieldValue->get_values_by_handler_and_field_variable($this->get_id(), 'use_score_as_progress');
11331
        if (!empty($useScore) && isset($useScore['value'])) {
11332
            $doUseScore = $useScore['value'];
11333
        }
11334
11335
        return $doUseScore;
11336
    }
11337
11338
    /**
11339
     * Get the user identifier (user_id or username
11340
     * Depends on scorm_api_username_as_student_id in app/config/configuration.php.
11341
     *
11342
     * @return string User ID or username, depending on configuration setting
11343
     */
11344
    public static function getUserIdentifierForExternalServices()
11345
    {
11346
        if (api_get_configuration_value('scorm_api_username_as_student_id')) {
11347
            return api_get_user_info(api_get_user_id())['username'];
11348
        } elseif (null != api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id')) {
11349
            $extraFieldValue = new ExtraFieldValue('user');
11350
            $extrafield = $extraFieldValue->get_values_by_handler_and_field_variable(api_get_user_id(), api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id'));
11351
11352
            return $extrafield['value'];
11353
        } else {
11354
            return api_get_user_id();
11355
        }
11356
    }
11357
11358
    /**
11359
     * Save the new order for learning path items.
11360
     *
11361
     * We have to update parent_item_id, previous_item_id, next_item_id, display_order in the database.
11362
     *
11363
     * @param array $orderList A associative array with item ID as key and parent ID as value.
11364
     * @param int   $courseId
11365
     */
11366
    public static function sortItemByOrderList(array $orderList, $courseId = 0)
11367
    {
11368
        $courseId = $courseId ?: api_get_course_int_id();
11369
        $itemList = new LpItemOrderList();
11370
11371
        foreach ($orderList as $id => $parentId) {
11372
            $item = new LpOrderItem($id, $parentId);
11373
            $itemList->add($item);
11374
        }
11375
11376
        $parents = $itemList->getListOfParents();
11377
11378
        foreach ($parents as $parentId) {
11379
            $sameParentLpItemList = $itemList->getItemWithSameParent($parentId);
11380
            $previous_item_id = 0;
11381
            for ($i = 0; $i < count($sameParentLpItemList->list); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

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

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

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
11382
                $item_id = $sameParentLpItemList->list[$i]->id;
11383
                // display_order
11384
                $display_order = $i + 1;
11385
                $itemList->setParametersForId($item_id, $display_order, 'display_order');
11386
                // previous_item_id
11387
                $itemList->setParametersForId($item_id, $previous_item_id, 'previous_item_id');
11388
                $previous_item_id = $item_id;
11389
                // next_item_id
11390
                $next_item_id = 0;
11391
                if ($i < count($sameParentLpItemList->list) - 1) {
11392
                    $next_item_id = $sameParentLpItemList->list[$i + 1]->id;
11393
                }
11394
                $itemList->setParametersForId($item_id, $next_item_id, 'next_item_id');
11395
            }
11396
        }
11397
11398
        $table = Database::get_course_table(TABLE_LP_ITEM);
11399
11400
        foreach ($itemList->list as $item) {
11401
            $params = [];
11402
            $params['display_order'] = $item->display_order;
11403
            $params['previous_item_id'] = $item->previous_item_id;
11404
            $params['next_item_id'] = $item->next_item_id;
11405
            $params['parent_item_id'] = $item->parent_item_id;
11406
11407
            Database::update(
11408
                $table,
11409
                $params,
11410
                [
11411
                    'iid = ? AND c_id = ? ' => [
11412
                        (int) $item->id,
11413
                        (int) $courseId,
11414
                    ],
11415
                ]
11416
            );
11417
        }
11418
    }
11419
11420
    /**
11421
     * Get the depth level of LP item.
11422
     *
11423
     * @param array $items
11424
     * @param int   $currentItemId
11425
     *
11426
     * @return int
11427
     */
11428
    private static function get_level_for_item($items, $currentItemId)
11429
    {
11430
        $parentItemId = 0;
11431
        if (isset($items[$currentItemId])) {
11432
            $parentItemId = $items[$currentItemId]->parent;
11433
        }
11434
11435
        if (0 == $parentItemId) {
11436
            return 0;
11437
        } else {
11438
            return self::get_level_for_item($items, $parentItemId) + 1;
11439
        }
11440
    }
11441
11442
    /**
11443
     * Generate the link for a learnpath category as course tool.
11444
     *
11445
     * @param int $categoryId
11446
     *
11447
     * @return string
11448
     */
11449
    private static function getCategoryLinkForTool($categoryId)
11450
    {
11451
        $categoryId = (int) $categoryId;
11452
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
11453
            .http_build_query(
11454
                [
11455
                    'action' => 'view_category',
11456
                    'id' => $categoryId,
11457
                ]
11458
            );
11459
11460
        return $link;
11461
    }
11462
11463
    /**
11464
     * Return the scorm item type object with spaces replaced with _
11465
     * The return result is use to build a css classname like scorm_type_$return.
11466
     *
11467
     * @param $in_type
11468
     *
11469
     * @return mixed
11470
     */
11471
    private static function format_scorm_type_item($in_type)
11472
    {
11473
        return str_replace(' ', '_', $in_type);
11474
    }
11475
11476
    /**
11477
     * Check and obtain the lp final item if exist.
11478
     *
11479
     * @return learnpathItem
11480
     */
11481
    private function getFinalItem()
11482
    {
11483
        if (empty($this->items)) {
11484
            return null;
11485
        }
11486
11487
        foreach ($this->items as $item) {
11488
            if ('final_item' !== $item->type) {
11489
                continue;
11490
            }
11491
11492
            return $item;
11493
        }
11494
    }
11495
11496
    /**
11497
     * Get the LP Final Item Template.
11498
     *
11499
     * @return string
11500
     */
11501
    private function getFinalItemTemplate()
11502
    {
11503
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
11504
    }
11505
11506
    /**
11507
     * Get the LP Final Item Url.
11508
     *
11509
     * @return string
11510
     */
11511
    private function getSavedFinalItem()
11512
    {
11513
        $finalItem = $this->getFinalItem();
11514
11515
        $repo = Container::getDocumentRepository();
11516
        /** @var CDocument $document */
11517
        $document = $repo->find($finalItem->path);
11518
11519
        if ($document && $document->getResourceNode()->hasResourceFile()) {
11520
            return  $repo->getResourceFileContent($document);
11521
        }
11522
11523
        return '';
11524
    }
11525
}
11526