Passed
Push — master ( ff47af...4fe9ef )
by Julito
08:21
created

learnpath   F

Complexity

Total Complexity 1811

Size/Duplication

Total Lines 13482
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 7382
dl 0
loc 13482
rs 0.8
c 4
b 0
f 0
wmc 1811

219 Methods

Rating   Name   Duplication   Size   Complexity  
F display_thread_form() 0 186 41
B save_last() 0 45 9
A set_jslib() 0 15 2
B set_current_item() 0 33 10
A display_lp_prerequisites_list() 0 31 5
F display_link_form() 0 169 33
F display_document_form() 0 346 72
F createReadOutText() 0 121 27
F displayFrmReadOutText() 0 273 59
F display_item_form() 0 231 39
A getCurrentBuildingModeURL() 0 11 5
A save_current() 0 32 6
B restart() 0 40 6
F display_item_prerequisites_form() 0 170 19
B display_move_item() 0 56 11
F display_hotpotatoes_form() 0 155 36
A set_encoding() 0 19 4
B get_js_dropdown_array() 0 78 6
B display_manipulate() 0 97 9
B get_documents() 0 99 2
F display_forum_form() 0 173 39
A set_maker() 0 14 2
F display_student_publication_form() 0 148 29
B save_item() 0 47 9
A categoryIsPublished() 0 25 2
B toggleCategoryPublish() 0 88 9
C categoryIsVisibleForStudent() 0 86 17
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
D display_edit_item() 0 121 21
A get_update_queue() 0 3 1
F get_link() 0 322 55
A switch_attempt_mode() 0 16 4
F create_document() 0 142 26
D prerequisites_match() 0 69 16
A previous() 0 11 1
A tree_array() 0 4 1
B move_down() 0 54 8
A open() 0 9 1
A update_default_scorm_commit() 0 25 4
B getChildrenToc() 0 58 11
A set_error_msg() 0 9 3
D stop_previous_item() 0 55 18
C start_current_item() 0 40 16
A set_publicated_on() 0 22 3
A get_author() 0 7 2
A set_prerequisite() 0 10 1
A set_modified_on() 0 10 1
A getHideTableOfContents() 0 3 1
A get_type() 0 8 3
A get_maker() 0 7 2
A get_progress_bar() 0 12 1
A set_preview_image() 0 11 1
C toggle_publish() 0 82 11
A get_type_static() 0 16 3
A sort_tree_array() 0 12 3
A get_items_status_list() 0 10 2
A get_theme() 0 7 2
A set_seriousgame_mode() 0 23 4
A get_preview_image() 0 7 2
A returnLpItemList() 0 15 2
B get_progress_bar_text() 0 29 7
A get_teacher_toc_buttons() 0 21 4
B create_tree_array() 0 38 11
A get_items_details_as_js() 0 8 2
A get_progress_bar_mode() 0 7 2
B set_terms_by_prefix() 0 68 10
A get_user_id() 0 7 2
A set_use_max_score() 0 12 1
A toggle_visibility() 0 14 2
A set_previous_item() 0 6 2
A display_document() 0 20 2
A set_theme() 0 11 1
A getChapterTypes() 0 4 1
B get_preview_image_path() 0 28 7
A set_author() 0 10 1
B get_iv_interactions_array() 0 54 8
A set_attempt_mode() 0 32 5
A update_display_order() 0 29 5
A getNameNoTags() 0 3 1
B generate_lp_folder() 0 52 7
F display_quiz_form() 0 167 35
A set_proximity() 0 15 2
A getProgressFromLpList() 0 32 4
C display_item() 0 98 16
B overview() 0 50 9
A set_expired_on() 0 23 3
B move_up() 0 51 8
A get_interactions_count_from_db() 0 16 2
B get_scorm_prereq_string() 0 73 11
B print_recursive() 0 40 10
B get_scorm_xml_node() 0 19 7
A has_audio() 0 11 3
C build_action_menu() 0 145 10
A get_view() 0 38 5
A get_flat_ordered_items_list() 0 35 5
A getProgress() 0 23 2
A display_resources() 0 55 2
A get_objectives_count_from_db() 0 16 2
F edit_document() 0 82 17
D move_item() 0 125 18
A update_scorm_debug() 0 23 4
A toggleCategoryVisibility() 0 14 2
C getParentToc() 0 64 13
A update_reinit() 0 23 4
D getListArrayToc() 0 76 11
A get_extension() 0 5 1
A get_view_id() 0 7 2
A generate_learning_path_folder() 0 23 3
A set_hide_toc_frame() 0 15 2
B get_attempt_mode() 0 21 9
A get_lp_session_id() 0 7 2
A return_new_tree() 0 34 4
A get_toc() 0 18 2
A get_name() 0 7 2
A set_name() 0 37 2
F processBuildMenuElements() 0 441 53
A get_total_items_count() 0 3 1
A get_first_item_id() 0 8 2
A get_course_int_id() 0 3 2
C get_exercises() 0 133 11
F edit_item() 0 212 22
A get_last() 0 10 2
A set_course_int_id() 0 3 1
F add_item() 0 214 15
A get_previous_index() 0 16 5
C isBlockedByPrerequisite() 0 68 13
A get_js_lib() 0 8 2
F first() 0 71 20
D get_package_type() 0 79 19
B get_links() 0 109 6
A get_current_item_id() 0 8 2
A getLastInFirstLevel() 0 13 2
F __construct() 0 308 47
A close() 0 13 2
C get_mediaplayer() 0 87 13
F autocomplete_parents() 0 101 17
B edit_item_prereq() 0 41 7
A get_id() 0 7 2
A get_next_item_id() 0 10 3
F is_lp_visible_for_student() 0 141 25
A get_complete_items_count() 0 24 5
B delete_item() 0 70 6
A getTotalItemsCountWithoutDirs() 0 11 3
F add_lp() 0 142 14
A getCourseCode() 0 3 1
C get_navigation_bar() 0 73 10
B get_next_index() 0 23 7
A get_common_index_terms_by_prefix() 0 17 3
A get_previous_item_id() 0 5 1
F delete() 0 116 18
A getEntity() 0 3 1
A get_student_publications() 0 41 3
A delete_children_items() 0 22 4
A getCategory() 0 7 1
A set_autolaunch() 0 27 2
B getCalculateScore() 0 47 6
A copy() 0 26 1
A getCategoryFromCourseIntoSelect() 0 15 4
A getAccumulateScormTime() 0 3 1
A createCategory() 0 31 1
C fixBlockedLinks() 0 64 11
A createForum() 0 21 1
B set_previous_step_as_prerequisite_for_all_items() 0 48 7
A updateCategory() 0 9 2
A getAccumulateWorkTime() 0 3 1
A getForum() 0 10 1
A getExercisesItems() 0 13 3
B getFinalItemForm() 0 91 4
A getSelectedIcon() 0 10 3
A getCountCategories() 0 10 2
A moveDownCategory() 0 11 2
F scormExport() 0 973 114
A getAccumulateWorkTimePrerequisite() 0 13 1
A getSelectedIconHtml() 0 12 2
A select_previous_item_id() 0 22 1
A getItemsForForm() 0 39 3
A getItem() 0 7 3
A getCategoryId() 0 3 1
A create_path() 0 14 5
B upload_image() 0 40 6
A setSubscribeUsers() 0 10 1
C rl_get_resource_name() 0 92 14
A getSavedFinalItem() 0 12 3
A cleanItemTitle() 0 5 1
A format_scorm_type_item() 0 3 1
A getCurrentItemParentNames() 0 13 3
A getSubscribeUsers() 0 3 1
A deleteCategory() 0 37 4
A getAccumulateWorkTimeTotalCourse() 0 10 1
A moveUpCategory() 0 11 2
A clear_prerequisites() 0 14 1
C scorm_export_to_pdf() 0 70 12
B isFirstOrLastItem() 0 32 6
C getCalculateStars() 0 80 12
A getFinalEvaluationItem() 0 12 3
A setItemTitle() 0 14 2
A getCategoryByCourse() 0 8 1
A getCategoryLinkForTool() 0 12 1
A getSubscriptionSettings() 0 14 2
B verify_document_size() 0 25 8
B get_forums() 0 107 8
A getCurrentAttempt() 0 10 2
A delete_lp_image() 0 17 5
A getFinalItem() 0 12 4
A setAccumulateScormTime() 0 11 1
A getFinalItemTemplate() 0 3 1
F rl_get_resource_link_for_learnpath() 0 171 32
A getAccumulateWorkTimeTotal() 0 11 1
A getLpFromSession() 0 28 5
A getCategories() 0 12 1
A setCategoryId() 0 10 1
A getIconSelect() 0 19 4
F exportToCourseBuildFormat() 0 199 31
A get_level_for_item() 0 11 3
A lpHasForum() 0 26 1
A setAccumulateWorkTime() 0 14 2

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

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

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

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

    return false;
}

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

Loading history...
4278
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4279
        $lp_id = (int) $lp_id;
4280
        $sql = "SELECT * FROM $tbl_lp
4281
                WHERE iid = $lp_id";
4282
        $result = Database::query($sql);
4283
4284
        if (Database::num_rows($result)) {
4285
            $row = Database::fetch_array($result);
4286
            $name = Database::escape_string($row['name']);
4287
            if ($set_visibility == 'i') {
4288
                $v = 0;
4289
            }
4290
            if ($set_visibility == 'v') {
4291
                $v = 1;
4292
            }
4293
4294
            $session_id = api_get_session_id();
4295
            $session_condition = api_get_session_condition($session_id);
4296
4297
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4298
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4299
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4300
4301
            $sql = "SELECT * FROM $tbl_tool
4302
                    WHERE
4303
                        c_id = $course_id AND
4304
                        (link = '$link' OR link = '$oldLink') AND
4305
                        image = 'scormbuilder.gif' AND
4306
                        (
4307
                            link LIKE '$link%' OR
4308
                            link LIKE '$oldLink%'
4309
                        )
4310
                        $session_condition
4311
                    ";
4312
4313
            $result = Database::query($sql);
4314
            $num = Database::num_rows($result);
4315
            if ($set_visibility == 'i' && $num > 0) {
4316
                $sql = "DELETE FROM $tbl_tool
4317
                        WHERE
4318
                            c_id = $course_id AND
4319
                            (link = '$link' OR link = '$oldLink') AND
4320
                            image='scormbuilder.gif'
4321
                            $session_condition";
4322
                Database::query($sql);
4323
            } elseif ($set_visibility == 'v' && $num == 0) {
4324
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4325
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4326
                Database::query($sql);
4327
                $insertId = Database::insert_id();
4328
                if ($insertId) {
4329
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4330
                    Database::query($sql);
4331
                }
4332
            } elseif ($set_visibility == 'v' && $num > 0) {
4333
                $sql = "UPDATE $tbl_tool SET
4334
                            c_id = $course_id,
4335
                            name = '$name',
4336
                            link = '$link',
4337
                            image = 'scormbuilder.gif',
4338
                            visibility = '$v',
4339
                            admin = '0',
4340
                            address = 'pastillegris.gif',
4341
                            added_tool = 0,
4342
                            session_id = $session_id
4343
                        WHERE
4344
                            c_id = ".$course_id." AND
4345
                            (link = '$link' OR link = '$oldLink') AND
4346
                            image='scormbuilder.gif'
4347
                            $session_condition
4348
                        ";
4349
                Database::query($sql);
4350
            } else {
4351
                // Parameter and database incompatible, do nothing, exit.
4352
                return false;
4353
            }
4354
        } else {
4355
            return false;
4356
        }
4357
    }
4358
4359
    /**
4360
     * Publishes a learnpath.
4361
     * Show or hide the learnpath category on the course homepage.
4362
     *
4363
     * @param int $id
4364
     * @param int $setVisibility
4365
     *
4366
     * @throws \Doctrine\ORM\NonUniqueResultException
4367
     * @throws \Doctrine\ORM\ORMException
4368
     * @throws \Doctrine\ORM\OptimisticLockException
4369
     * @throws \Doctrine\ORM\TransactionRequiredException
4370
     *
4371
     * @return bool
4372
     */
4373
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4374
    {
4375
        $courseId = api_get_course_int_id();
4376
        $sessionId = api_get_session_id();
4377
        $sessionCondition = api_get_session_condition(
4378
            $sessionId,
4379
            true,
4380
            false,
4381
            't.sessionId'
4382
        );
4383
4384
        $em = Database::getManager();
4385
4386
        /** @var CLpCategory $category */
4387
        $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
4388
4389
        if (!$category) {
4390
            return false;
4391
        }
4392
4393
        if (empty($courseId)) {
4394
            return false;
4395
        }
4396
4397
        $link = self::getCategoryLinkForTool($id);
4398
4399
        /** @var CTool $tool */
4400
        $tool = $em->createQuery("
4401
                SELECT t FROM ChamiloCourseBundle:CTool t
4402
                WHERE
4403
                    t.course = :course AND
4404
                    t.link = :link1 AND
4405
                    t.image LIKE 'lp_category.%' AND
4406
                    t.link LIKE :link2
4407
                    $sessionCondition
4408
            ")
4409
            ->setParameters([
4410
                'course' => $courseId,
4411
                'link1' => $link,
4412
                'link2' => "$link%",
4413
            ])
4414
            ->getOneOrNullResult();
4415
4416
        if ($setVisibility == 0 && $tool) {
4417
            $em->remove($tool);
4418
            $em->flush();
4419
4420
            return true;
4421
        }
4422
4423
        if ($setVisibility == 1 && !$tool) {
4424
            $tool = new CTool();
4425
            $tool
4426
                ->setCategory('authoring')
4427
                ->setCourse(api_get_course_entity($courseId))
4428
                ->setName(strip_tags($category->getName()))
4429
                ->setLink($link)
4430
                ->setImage('lp_category.png')
4431
                ->setVisibility(1)
4432
                ->setAdmin(0)
4433
                ->setAddress('pastillegris.gif')
4434
                ->setAddedTool(0)
4435
                ->setSessionId($sessionId)
4436
                ->setTarget('_self');
4437
4438
            $em->persist($tool);
4439
            $em->flush();
4440
4441
            $tool->setId($tool->getIid());
4442
4443
            $em->persist($tool);
4444
            $em->flush();
4445
4446
            return true;
4447
        }
4448
4449
        if ($setVisibility == 1 && $tool) {
4450
            $tool
4451
                ->setName(strip_tags($category->getName()))
4452
                ->setVisibility(1);
4453
4454
            $em->persist($tool);
4455
            $em->flush();
4456
4457
            return true;
4458
        }
4459
4460
        return false;
4461
    }
4462
4463
    /**
4464
     * Check if the learnpath category is visible for a user.
4465
     *
4466
     * @param int
4467
     * @param int
4468
     *
4469
     * @return bool
4470
     */
4471
    public static function categoryIsVisibleForStudent(
4472
        CLpCategory $category,
4473
        User $user,
4474
        $courseId = 0,
4475
        $sessionId = 0
4476
    ) {
4477
        if (empty($category)) {
4478
            return false;
4479
        }
4480
4481
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4482
4483
        if ($isAllowedToEdit) {
4484
            return true;
4485
        }
4486
4487
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4488
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4489
4490
        $courseInfo = api_get_course_info_by_id($courseId);
4491
4492
        $categoryVisibility = api_get_item_visibility(
4493
            $courseInfo,
4494
            TOOL_LEARNPATH_CATEGORY,
4495
            $category->getId(),
4496
            $sessionId
4497
        );
4498
4499
        if ($categoryVisibility !== 1 && $categoryVisibility != -1) {
4500
            return false;
4501
        }
4502
4503
        $subscriptionSettings = self::getSubscriptionSettings();
4504
4505
        if ($subscriptionSettings['allow_add_users_to_lp_category'] == false) {
4506
            return true;
4507
        }
4508
4509
        $users = $category->getUsers();
4510
4511
        if (empty($users) || !$users->count()) {
4512
            return true;
4513
        }
4514
4515
        if ($category->hasUserAdded($user)) {
4516
            return true;
4517
        }
4518
4519
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4520
        if (!empty($groups)) {
4521
            $em = Database::getManager();
4522
4523
            /** @var ItemPropertyRepository $itemRepo */
4524
            $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4525
4526
            /** @var CourseRepository $courseRepo */
4527
            $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4528
            $session = null;
4529
            if (!empty($sessionId)) {
4530
                $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4531
            }
4532
4533
            $course = $courseRepo->find($courseId);
4534
4535
            // Subscribed groups to a LP
4536
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4537
                TOOL_LEARNPATH_CATEGORY,
4538
                $category->getId(),
4539
                $course,
4540
                $session
4541
            );
4542
4543
            if (!empty($subscribedGroupsInLp)) {
4544
                $groups = array_column($groups, 'iid');
4545
                /** @var CItemProperty $item */
4546
                foreach ($subscribedGroupsInLp as $item) {
4547
                    if ($item->getGroup() &&
4548
                        in_array($item->getGroup()->getId(), $groups)
4549
                    ) {
4550
                        return true;
4551
                    }
4552
                }
4553
            }
4554
        }
4555
4556
        return false;
4557
    }
4558
4559
    /**
4560
     * Check if a learnpath category is published as course tool.
4561
     *
4562
     * @param int $courseId
4563
     *
4564
     * @return bool
4565
     */
4566
    public static function categoryIsPublished(CLpCategory $category, $courseId)
4567
    {
4568
        return false;
4569
        $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...
4570
        $em = Database::getManager();
4571
4572
        $tools = $em
4573
            ->createQuery("
4574
                SELECT t FROM ChamiloCourseBundle:CTool t
4575
                WHERE t.course = :course AND
4576
                    t.name = :name AND
4577
                    t.image LIKE 'lp_category.%' AND
4578
                    t.link LIKE :link
4579
            ")
4580
            ->setParameters([
4581
                'course' => $courseId,
4582
                'name' => strip_tags($category->getName()),
4583
                'link' => "$link%",
4584
            ])
4585
            ->getResult();
4586
4587
        /** @var CTool $tool */
4588
        $tool = current($tools);
4589
4590
        return $tool ? $tool->getVisibility() : false;
4591
    }
4592
4593
    /**
4594
     * Restart the whole learnpath. Return the URL of the first element.
4595
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4596
     * To use a similar method  statically, use the create_new_attempt() method.
4597
     *
4598
     * @return bool
4599
     */
4600
    public function restart()
4601
    {
4602
        if ($this->debug > 0) {
4603
            error_log('In learnpath::restart()', 0);
4604
        }
4605
        // TODO
4606
        // Call autosave method to save the current progress.
4607
        //$this->index = 0;
4608
        if (api_is_invitee()) {
4609
            return false;
4610
        }
4611
        $session_id = api_get_session_id();
4612
        $course_id = api_get_course_int_id();
4613
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4614
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4615
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4616
        if ($this->debug > 2) {
4617
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4618
        }
4619
        Database::query($sql);
4620
        $view_id = Database::insert_id();
4621
4622
        if ($view_id) {
4623
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4624
            Database::query($sql);
4625
            $this->lp_view_id = $view_id;
4626
            $this->attempt = $this->attempt + 1;
4627
        } else {
4628
            $this->error = 'Could not insert into item_view table...';
4629
4630
            return false;
4631
        }
4632
        $this->autocomplete_parents($this->current);
4633
        foreach ($this->items as $index => $dummy) {
4634
            $this->items[$index]->restart();
4635
            $this->items[$index]->set_lp_view($this->lp_view_id);
4636
        }
4637
        $this->first();
4638
4639
        return true;
4640
    }
4641
4642
    /**
4643
     * Saves the current item.
4644
     *
4645
     * @return bool
4646
     */
4647
    public function save_current()
4648
    {
4649
        $debug = $this->debug;
4650
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4651
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4652
        if ($debug) {
4653
            error_log('save_current() saving item '.$this->current, 0);
4654
            error_log(''.print_r($this->items, true), 0);
4655
        }
4656
        if (isset($this->items[$this->current]) &&
4657
            is_object($this->items[$this->current])
4658
        ) {
4659
            if ($debug) {
4660
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4661
            }
4662
4663
            $res = $this->items[$this->current]->save(
4664
                false,
4665
                $this->prerequisites_match($this->current)
4666
            );
4667
            $this->autocomplete_parents($this->current);
4668
            $status = $this->items[$this->current]->get_status();
4669
            $this->update_queue[$this->current] = $status;
4670
4671
            if ($debug) {
4672
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4673
            }
4674
4675
            return $res;
4676
        }
4677
4678
        return false;
4679
    }
4680
4681
    /**
4682
     * Saves the given item.
4683
     *
4684
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4685
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4686
     *
4687
     * @return bool
4688
     */
4689
    public function save_item($item_id = null, $from_outside = true)
4690
    {
4691
        $debug = $this->debug;
4692
        if ($debug) {
4693
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4694
        }
4695
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4696
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4697
        if (empty($item_id)) {
4698
            $item_id = (int) $_REQUEST['id'];
4699
        }
4700
4701
        if (empty($item_id)) {
4702
            $item_id = $this->get_current_item_id();
4703
        }
4704
        if (isset($this->items[$item_id]) &&
4705
            is_object($this->items[$item_id])
4706
        ) {
4707
            if ($debug) {
4708
                error_log('Object exists');
4709
            }
4710
4711
            // Saving the item.
4712
            $res = $this->items[$item_id]->save(
4713
                $from_outside,
4714
                $this->prerequisites_match($item_id)
4715
            );
4716
4717
            if ($debug) {
4718
                error_log('update_queue before:');
4719
                error_log(print_r($this->update_queue, 1));
4720
            }
4721
            $this->autocomplete_parents($item_id);
4722
4723
            $status = $this->items[$item_id]->get_status();
4724
            $this->update_queue[$item_id] = $status;
4725
4726
            if ($debug) {
4727
                error_log('get_status(): '.$status);
4728
                error_log('update_queue after:');
4729
                error_log(print_r($this->update_queue, 1));
4730
            }
4731
4732
            return $res;
4733
        }
4734
4735
        return false;
4736
    }
4737
4738
    /**
4739
     * Saves the last item seen's ID only in case.
4740
     */
4741
    public function save_last()
4742
    {
4743
        $course_id = api_get_course_int_id();
4744
        $debug = $this->debug;
4745
        if ($debug) {
4746
            error_log('In learnpath::save_last()', 0);
4747
        }
4748
        $session_condition = api_get_session_condition(
4749
            api_get_session_id(),
4750
            true,
4751
            false
4752
        );
4753
        $table = Database::get_course_table(TABLE_LP_VIEW);
4754
4755
        if (isset($this->current) && !api_is_invitee()) {
4756
            if ($debug) {
4757
                error_log('Saving current item ('.$this->current.') for later review', 0);
4758
            }
4759
            $sql = "UPDATE $table SET
4760
                        last_item = ".$this->get_current_item_id()."
4761
                    WHERE
4762
                        c_id = $course_id AND
4763
                        lp_id = ".$this->get_id()." AND
4764
                        user_id = ".$this->get_user_id()." ".$session_condition;
4765
4766
            if ($debug) {
4767
                error_log('Saving last item seen : '.$sql, 0);
4768
            }
4769
            Database::query($sql);
4770
        }
4771
4772
        if (!api_is_invitee()) {
4773
            // Save progress.
4774
            list($progress) = $this->get_progress_bar_text('%');
4775
            if ($progress >= 0 && $progress <= 100) {
4776
                $progress = (int) $progress;
4777
                $sql = "UPDATE $table SET
4778
                            progress = $progress
4779
                        WHERE
4780
                            c_id = $course_id AND
4781
                            lp_id = ".$this->get_id()." AND
4782
                            user_id = ".$this->get_user_id()." ".$session_condition;
4783
                // Ignore errors as some tables might not have the progress field just yet.
4784
                Database::query($sql);
4785
                $this->progress_db = $progress;
4786
            }
4787
        }
4788
    }
4789
4790
    /**
4791
     * Sets the current item ID (checks if valid and authorized first).
4792
     *
4793
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
4794
     */
4795
    public function set_current_item($item_id = null)
4796
    {
4797
        $debug = $this->debug;
4798
        if ($debug) {
4799
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
4800
        }
4801
        if (empty($item_id)) {
4802
            if ($debug) {
4803
                error_log('No new current item given, ignore...', 0);
4804
            }
4805
            // Do nothing.
4806
        } else {
4807
            if ($debug) {
4808
                error_log('New current item given is '.$item_id.'...', 0);
4809
            }
4810
            if (is_numeric($item_id)) {
4811
                $item_id = (int) $item_id;
4812
                // TODO: Check in database here.
4813
                $this->last = $this->current;
4814
                $this->current = $item_id;
4815
                // TODO: Update $this->index as well.
4816
                foreach ($this->ordered_items as $index => $item) {
4817
                    if ($item == $this->current) {
4818
                        $this->index = $index;
4819
                        break;
4820
                    }
4821
                }
4822
                if ($debug) {
4823
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
4824
                }
4825
            } else {
4826
                if ($debug) {
4827
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
4828
                }
4829
            }
4830
        }
4831
    }
4832
4833
    /**
4834
     * Sets the encoding.
4835
     *
4836
     * @param string $enc New encoding
4837
     *
4838
     * @return bool
4839
     *
4840
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
4841
     */
4842
    public function set_encoding($enc = 'UTF-8')
4843
    {
4844
        $enc = api_refine_encoding_id($enc);
4845
        if (empty($enc)) {
4846
            $enc = api_get_system_encoding();
4847
        }
4848
        if (api_is_encoding_supported($enc)) {
4849
            $lp = $this->get_id();
4850
            if ($lp != 0) {
4851
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4852
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
4853
                        WHERE iid = ".$lp;
4854
                $res = Database::query($sql);
4855
4856
                return $res;
4857
            }
4858
        }
4859
4860
        return false;
4861
    }
4862
4863
    /**
4864
     * Sets the JS lib setting in the database directly.
4865
     * This is the JavaScript library file this lp needs to load on startup.
4866
     *
4867
     * @param string $lib Proximity setting
4868
     *
4869
     * @return bool True on update success. False otherwise.
4870
     */
4871
    public function set_jslib($lib = '')
4872
    {
4873
        $lp = $this->get_id();
4874
4875
        if ($lp != 0) {
4876
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4877
            $lib = Database::escape_string($lib);
4878
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
4879
                    WHERE iid = $lp";
4880
            $res = Database::query($sql);
4881
4882
            return $res;
4883
        }
4884
4885
        return false;
4886
    }
4887
4888
    /**
4889
     * Sets the name of the LP maker (publisher) (and save).
4890
     *
4891
     * @param string $name Optional string giving the new content_maker of this learnpath
4892
     *
4893
     * @return bool True
4894
     */
4895
    public function set_maker($name = '')
4896
    {
4897
        if (empty($name)) {
4898
            return false;
4899
        }
4900
        $this->maker = $name;
4901
        $table = Database::get_course_table(TABLE_LP_MAIN);
4902
        $lp_id = $this->get_id();
4903
        $sql = "UPDATE $table SET
4904
                content_maker = '".Database::escape_string($this->maker)."'
4905
                WHERE iid = $lp_id";
4906
        Database::query($sql);
4907
4908
        return true;
4909
    }
4910
4911
    /**
4912
     * Sets the name of the current learnpath (and save).
4913
     *
4914
     * @param string $name Optional string giving the new name of this learnpath
4915
     *
4916
     * @return bool True/False
4917
     */
4918
    public function set_name($name = null)
4919
    {
4920
        if (empty($name)) {
4921
            return false;
4922
        }
4923
        $this->name = $name;
4924
4925
        $lp_id = $this->get_id();
4926
4927
        $repo = Container::getLpRepository();
4928
        /** @var CLp $lp */
4929
        $lp = $repo->find($lp_id);
4930
        $lp->setName($name);
4931
        $repo->updateNodeForResource($lp);
4932
4933
        /*
4934
        $course_id = $this->course_info['real_id'];
4935
        $sql = "UPDATE $lp_table SET
4936
            name = '$name'
4937
            WHERE iid = $lp_id";
4938
        $result = Database::query($sql);
4939
        // If the lp is visible on the homepage, change his name there.
4940
        if (Database::affected_rows($result)) {
4941
        $session_id = api_get_session_id();
4942
        $session_condition = api_get_session_condition($session_id);
4943
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4944
        $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4945
        $sql = "UPDATE $tbl_tool SET name = '$name'
4946
        	    WHERE
4947
        	        c_id = $course_id AND
4948
        	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
4949
        Database::query($sql);*/
4950
4951
        //return true;
4952
        //}
4953
4954
        return false;
4955
    }
4956
4957
    /**
4958
     * Set index specified prefix terms for all items in this path.
4959
     *
4960
     * @param string $terms_string Comma-separated list of terms
4961
     * @param string $prefix       Xapian term prefix
4962
     *
4963
     * @return bool False on error, true otherwise
4964
     */
4965
    public function set_terms_by_prefix($terms_string, $prefix)
4966
    {
4967
        $course_id = api_get_course_int_id();
4968
        if (api_get_setting('search_enabled') !== 'true') {
4969
            return false;
4970
        }
4971
4972
        if (!extension_loaded('xapian')) {
4973
            return false;
4974
        }
4975
4976
        $terms_string = trim($terms_string);
4977
        $terms = explode(',', $terms_string);
4978
        array_walk($terms, 'trim_value');
4979
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
4980
4981
        // Don't do anything if no change, verify only at DB, not the search engine.
4982
        if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
4983
            return false;
4984
        }
4985
4986
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
4987
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
4988
4989
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
4990
        // TODO: Make query secure agains XSS : use member attr instead of post var.
4991
        $lp_id = (int) $_POST['lp_id'];
4992
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
4993
        $result = Database::query($sql);
4994
        $di = new ChamiloIndexer();
4995
4996
        while ($lp_item = Database::fetch_array($result)) {
4997
            // Get search_did.
4998
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
4999
            $sql = 'SELECT * FROM %s
5000
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5001
                    LIMIT 1';
5002
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5003
5004
            //echo $sql; echo '<br>';
5005
            $res = Database::query($sql);
5006
            if (Database::num_rows($res) > 0) {
5007
                $se_ref = Database::fetch_array($res);
5008
                // Compare terms.
5009
                $doc = $di->get_document($se_ref['search_did']);
5010
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5011
                $xterms = [];
5012
                foreach ($xapian_terms as $xapian_term) {
5013
                    $xterms[] = substr($xapian_term['name'], 1);
5014
                }
5015
5016
                $dterms = $terms;
5017
                $missing_terms = array_diff($dterms, $xterms);
5018
                $deprecated_terms = array_diff($xterms, $dterms);
5019
5020
                // Save it to search engine.
5021
                foreach ($missing_terms as $term) {
5022
                    $doc->add_term($prefix.$term, 1);
5023
                }
5024
                foreach ($deprecated_terms as $term) {
5025
                    $doc->remove_term($prefix.$term);
5026
                }
5027
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5028
                $di->getDb()->flush();
5029
            }
5030
        }
5031
5032
        return true;
5033
    }
5034
5035
    /**
5036
     * Sets the theme of the LP (local/remote) (and save).
5037
     *
5038
     * @param string $name Optional string giving the new theme of this learnpath
5039
     *
5040
     * @return bool Returns true if theme name is not empty
5041
     */
5042
    public function set_theme($name = '')
5043
    {
5044
        $this->theme = $name;
5045
        $table = Database::get_course_table(TABLE_LP_MAIN);
5046
        $lp_id = $this->get_id();
5047
        $sql = "UPDATE $table
5048
                SET theme = '".Database::escape_string($this->theme)."'
5049
                WHERE iid = $lp_id";
5050
        Database::query($sql);
5051
5052
        return true;
5053
    }
5054
5055
    /**
5056
     * Sets the image of an LP (and save).
5057
     *
5058
     * @param string $name Optional string giving the new image of this learnpath
5059
     *
5060
     * @return bool Returns true if theme name is not empty
5061
     */
5062
    public function set_preview_image($name = '')
5063
    {
5064
        $this->preview_image = $name;
5065
        $table = Database::get_course_table(TABLE_LP_MAIN);
5066
        $lp_id = $this->get_id();
5067
        $sql = "UPDATE $table SET
5068
                preview_image = '".Database::escape_string($this->preview_image)."'
5069
                WHERE iid = $lp_id";
5070
        Database::query($sql);
5071
5072
        return true;
5073
    }
5074
5075
    /**
5076
     * Sets the author of a LP (and save).
5077
     *
5078
     * @param string $name Optional string giving the new author of this learnpath
5079
     *
5080
     * @return bool Returns true if author's name is not empty
5081
     */
5082
    public function set_author($name = '')
5083
    {
5084
        $this->author = $name;
5085
        $table = Database::get_course_table(TABLE_LP_MAIN);
5086
        $lp_id = $this->get_id();
5087
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5088
                WHERE iid = $lp_id";
5089
        Database::query($sql);
5090
5091
        return true;
5092
    }
5093
5094
    /**
5095
     * Sets the hide_toc_frame parameter of a LP (and save).
5096
     *
5097
     * @param int $hide 1 if frame is hidden 0 then else
5098
     *
5099
     * @return bool Returns true if author's name is not empty
5100
     */
5101
    public function set_hide_toc_frame($hide)
5102
    {
5103
        if (intval($hide) == $hide) {
5104
            $this->hide_toc_frame = $hide;
5105
            $table = Database::get_course_table(TABLE_LP_MAIN);
5106
            $lp_id = $this->get_id();
5107
            $sql = "UPDATE $table SET
5108
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5109
                    WHERE iid = $lp_id";
5110
            Database::query($sql);
5111
5112
            return true;
5113
        }
5114
5115
        return false;
5116
    }
5117
5118
    /**
5119
     * Sets the prerequisite of a LP (and save).
5120
     *
5121
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5122
     *
5123
     * @return bool returns true if prerequisite is not empty
5124
     */
5125
    public function set_prerequisite($prerequisite)
5126
    {
5127
        $this->prerequisite = (int) $prerequisite;
5128
        $table = Database::get_course_table(TABLE_LP_MAIN);
5129
        $lp_id = $this->get_id();
5130
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5131
                WHERE iid = $lp_id";
5132
        Database::query($sql);
5133
5134
        return true;
5135
    }
5136
5137
    /**
5138
     * Sets the location/proximity of the LP (local/remote) (and save).
5139
     *
5140
     * @param string $name Optional string giving the new location of this learnpath
5141
     *
5142
     * @return bool True on success / False on error
5143
     */
5144
    public function set_proximity($name = '')
5145
    {
5146
        if (empty($name)) {
5147
            return false;
5148
        }
5149
5150
        $this->proximity = $name;
5151
        $table = Database::get_course_table(TABLE_LP_MAIN);
5152
        $lp_id = $this->get_id();
5153
        $sql = "UPDATE $table SET
5154
                    content_local = '".Database::escape_string($name)."'
5155
                WHERE iid = $lp_id";
5156
        Database::query($sql);
5157
5158
        return true;
5159
    }
5160
5161
    /**
5162
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5163
     *
5164
     * @param int $id DB ID of the item
5165
     */
5166
    public function set_previous_item($id)
5167
    {
5168
        if ($this->debug > 0) {
5169
            error_log('In learnpath::set_previous_item()', 0);
5170
        }
5171
        $this->last = $id;
5172
    }
5173
5174
    /**
5175
     * Sets use_max_score.
5176
     *
5177
     * @param int $use_max_score Optional string giving the new location of this learnpath
5178
     *
5179
     * @return bool True on success / False on error
5180
     */
5181
    public function set_use_max_score($use_max_score = 1)
5182
    {
5183
        $use_max_score = (int) $use_max_score;
5184
        $this->use_max_score = $use_max_score;
5185
        $table = Database::get_course_table(TABLE_LP_MAIN);
5186
        $lp_id = $this->get_id();
5187
        $sql = "UPDATE $table SET
5188
                    use_max_score = '".$this->use_max_score."'
5189
                WHERE iid = $lp_id";
5190
        Database::query($sql);
5191
5192
        return true;
5193
    }
5194
5195
    /**
5196
     * Sets and saves the expired_on date.
5197
     *
5198
     * @param string $expired_on Optional string giving the new author of this learnpath
5199
     *
5200
     * @throws \Doctrine\ORM\OptimisticLockException
5201
     *
5202
     * @return bool Returns true if author's name is not empty
5203
     */
5204
    public function set_expired_on($expired_on)
5205
    {
5206
        $em = Database::getManager();
5207
        /** @var CLp $lp */
5208
        $lp = $em
5209
            ->getRepository('ChamiloCourseBundle:CLp')
5210
            ->findOneBy(
5211
                [
5212
                    'iid' => $this->get_id(),
5213
                ]
5214
            );
5215
5216
        if (!$lp) {
5217
            return false;
5218
        }
5219
5220
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5221
5222
        $lp->setExpiredOn($this->expired_on);
5223
        $em->persist($lp);
5224
        $em->flush();
5225
5226
        return true;
5227
    }
5228
5229
    /**
5230
     * Sets and saves the publicated_on date.
5231
     *
5232
     * @param string $publicated_on Optional string giving the new author of this learnpath
5233
     *
5234
     * @throws \Doctrine\ORM\OptimisticLockException
5235
     *
5236
     * @return bool Returns true if author's name is not empty
5237
     */
5238
    public function set_publicated_on($publicated_on)
5239
    {
5240
        $em = Database::getManager();
5241
        /** @var CLp $lp */
5242
        $lp = $em
5243
            ->getRepository('ChamiloCourseBundle:CLp')
5244
            ->findOneBy(
5245
                [
5246
                    'iid' => $this->get_id(),
5247
                ]
5248
            );
5249
5250
        if (!$lp) {
5251
            return false;
5252
        }
5253
5254
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5255
        $lp->setPublicatedOn($this->publicated_on);
5256
        $em->persist($lp);
5257
        $em->flush();
5258
5259
        return true;
5260
    }
5261
5262
    /**
5263
     * Sets and saves the expired_on date.
5264
     *
5265
     * @return bool Returns true if author's name is not empty
5266
     */
5267
    public function set_modified_on()
5268
    {
5269
        $this->modified_on = api_get_utc_datetime();
5270
        $table = Database::get_course_table(TABLE_LP_MAIN);
5271
        $lp_id = $this->get_id();
5272
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5273
                WHERE iid = $lp_id";
5274
        Database::query($sql);
5275
5276
        return true;
5277
    }
5278
5279
    /**
5280
     * Sets the object's error message.
5281
     *
5282
     * @param string $error Error message. If empty, reinits the error string
5283
     */
5284
    public function set_error_msg($error = '')
5285
    {
5286
        if ($this->debug > 0) {
5287
            error_log('In learnpath::set_error_msg()', 0);
5288
        }
5289
        if (empty($error)) {
5290
            $this->error = '';
5291
        } else {
5292
            $this->error .= $error;
5293
        }
5294
    }
5295
5296
    /**
5297
     * Launches the current item if not 'sco'
5298
     * (starts timer and make sure there is a record ready in the DB).
5299
     *
5300
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5301
     *
5302
     * @return bool
5303
     */
5304
    public function start_current_item($allow_new_attempt = false)
5305
    {
5306
        $debug = $this->debug;
5307
        if ($debug) {
5308
            error_log('In learnpath::start_current_item()');
5309
            error_log('current: '.$this->current);
5310
        }
5311
        if ($this->current != 0 && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5312
            $type = $this->get_type();
5313
            $item_type = $this->items[$this->current]->get_type();
5314
            if (($type == 2 && $item_type != 'sco') ||
5315
                ($type == 3 && $item_type != 'au') ||
5316
                ($type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES)
5317
            ) {
5318
                if ($debug) {
5319
                    error_log('item type: '.$item_type);
5320
                    error_log('lp type: '.$type);
5321
                }
5322
                $this->items[$this->current]->open($allow_new_attempt);
5323
                $this->autocomplete_parents($this->current);
5324
                $prereq_check = $this->prerequisites_match($this->current);
5325
                if ($debug) {
5326
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5327
                }
5328
                $this->items[$this->current]->save(false, $prereq_check);
5329
            }
5330
            // If sco, then it is supposed to have been updated by some other call.
5331
            if ($item_type == 'sco') {
5332
                $this->items[$this->current]->restart();
5333
            }
5334
        }
5335
        if ($debug) {
5336
            error_log('lp_view_session_id');
5337
            error_log($this->lp_view_session_id);
5338
            error_log('api session id');
5339
            error_log(api_get_session_id());
5340
            error_log('End of learnpath::start_current_item()');
5341
        }
5342
5343
        return true;
5344
    }
5345
5346
    /**
5347
     * Stops the processing and counters for the old item (as held in $this->last).
5348
     *
5349
     * @return bool True/False
5350
     */
5351
    public function stop_previous_item()
5352
    {
5353
        $debug = $this->debug;
5354
        if ($debug) {
5355
            error_log('In learnpath::stop_previous_item()', 0);
5356
        }
5357
5358
        if ($this->last != 0 && $this->last != $this->current &&
5359
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5360
        ) {
5361
            if ($debug) {
5362
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5363
            }
5364
            switch ($this->get_type()) {
5365
                case '3':
5366
                    if ($this->items[$this->last]->get_type() != 'au') {
5367
                        if ($debug) {
5368
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5369
                        }
5370
                        $this->items[$this->last]->close();
5371
                    } else {
5372
                        if ($debug) {
5373
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5374
                        }
5375
                    }
5376
                    break;
5377
                case '2':
5378
                    if ($this->items[$this->last]->get_type() != 'sco') {
5379
                        if ($debug) {
5380
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5381
                        }
5382
                        $this->items[$this->last]->close();
5383
                    } else {
5384
                        if ($debug) {
5385
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5386
                        }
5387
                    }
5388
                    break;
5389
                case '1':
5390
                default:
5391
                    if ($debug) {
5392
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5393
                    }
5394
                    $this->items[$this->last]->close();
5395
                    break;
5396
            }
5397
        } else {
5398
            if ($debug) {
5399
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5400
            }
5401
5402
            return false;
5403
        }
5404
5405
        return true;
5406
    }
5407
5408
    /**
5409
     * Updates the default view mode from fullscreen to embedded and inversely.
5410
     *
5411
     * @return string The current default view mode ('fullscreen' or 'embedded')
5412
     */
5413
    public function update_default_view_mode()
5414
    {
5415
        $table = Database::get_course_table(TABLE_LP_MAIN);
5416
        $sql = "SELECT * FROM $table
5417
                WHERE iid = ".$this->get_id();
5418
        $res = Database::query($sql);
5419
        if (Database::num_rows($res) > 0) {
5420
            $row = Database::fetch_array($res);
5421
            $default_view_mode = $row['default_view_mod'];
5422
            $view_mode = $default_view_mode;
5423
            switch ($default_view_mode) {
5424
                case 'fullscreen': // default with popup
5425
                    $view_mode = 'embedded';
5426
                    break;
5427
                case 'embedded': // default view with left menu
5428
                    $view_mode = 'embedframe';
5429
                    break;
5430
                case 'embedframe': //folded menu
5431
                    $view_mode = 'impress';
5432
                    break;
5433
                case 'impress':
5434
                    $view_mode = 'fullscreen';
5435
                    break;
5436
            }
5437
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5438
                    WHERE iid = ".$this->get_id();
5439
            Database::query($sql);
5440
            $this->mode = $view_mode;
5441
5442
            return $view_mode;
5443
        }
5444
5445
        return -1;
5446
    }
5447
5448
    /**
5449
     * Updates the default behaviour about auto-commiting SCORM updates.
5450
     *
5451
     * @return bool True if auto-commit has been set to 'on', false otherwise
5452
     */
5453
    public function update_default_scorm_commit()
5454
    {
5455
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5456
        $sql = "SELECT * FROM $lp_table
5457
                WHERE iid = ".$this->get_id();
5458
        $res = Database::query($sql);
5459
        if (Database::num_rows($res) > 0) {
5460
            $row = Database::fetch_array($res);
5461
            $force = $row['force_commit'];
5462
            if ($force == 1) {
5463
                $force = 0;
5464
                $force_return = false;
5465
            } elseif ($force == 0) {
5466
                $force = 1;
5467
                $force_return = true;
5468
            }
5469
            $sql = "UPDATE $lp_table SET force_commit = $force
5470
                    WHERE iid = ".$this->get_id();
5471
            Database::query($sql);
5472
            $this->force_commit = $force_return;
5473
5474
            return $force_return;
5475
        }
5476
5477
        return -1;
5478
    }
5479
5480
    /**
5481
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5482
     *
5483
     * @return bool True on success, false on failure
5484
     */
5485
    public function update_display_order()
5486
    {
5487
        $course_id = api_get_course_int_id();
5488
        $table = Database::get_course_table(TABLE_LP_MAIN);
5489
        $sql = "SELECT * FROM $table
5490
                WHERE c_id = $course_id
5491
                ORDER BY display_order";
5492
        $res = Database::query($sql);
5493
        if ($res === false) {
5494
            return false;
5495
        }
5496
5497
        $num = Database::num_rows($res);
5498
        // First check the order is correct, globally (might be wrong because
5499
        // of versions < 1.8.4).
5500
        if ($num > 0) {
5501
            $i = 1;
5502
            while ($row = Database::fetch_array($res)) {
5503
                if ($row['display_order'] != $i) {
5504
                    // If we find a gap in the order, we need to fix it.
5505
                    $sql = "UPDATE $table SET display_order = $i
5506
                            WHERE iid = ".$row['iid'];
5507
                    Database::query($sql);
5508
                }
5509
                $i++;
5510
            }
5511
        }
5512
5513
        return true;
5514
    }
5515
5516
    /**
5517
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5518
     *
5519
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5520
     */
5521
    public function update_reinit()
5522
    {
5523
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5524
        $sql = "SELECT * FROM $lp_table
5525
                WHERE iid = ".$this->get_id();
5526
        $res = Database::query($sql);
5527
        if (Database::num_rows($res) > 0) {
5528
            $row = Database::fetch_array($res);
5529
            $force = $row['prevent_reinit'];
5530
            if ($force == 1) {
5531
                $force = 0;
5532
            } elseif ($force == 0) {
5533
                $force = 1;
5534
            }
5535
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5536
                    WHERE iid = ".$this->get_id();
5537
            Database::query($sql);
5538
            $this->prevent_reinit = $force;
5539
5540
            return $force;
5541
        }
5542
5543
        return -1;
5544
    }
5545
5546
    /**
5547
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5548
     *
5549
     * @return string 'single', 'multi' or 'seriousgame'
5550
     *
5551
     * @author ndiechburg <[email protected]>
5552
     */
5553
    public function get_attempt_mode()
5554
    {
5555
        //Set default value for seriousgame_mode
5556
        if (!isset($this->seriousgame_mode)) {
5557
            $this->seriousgame_mode = 0;
5558
        }
5559
        // Set default value for prevent_reinit
5560
        if (!isset($this->prevent_reinit)) {
5561
            $this->prevent_reinit = 1;
5562
        }
5563
        if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
5564
            return 'seriousgame';
5565
        }
5566
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
5567
            return 'single';
5568
        }
5569
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
5570
            return 'multiple';
5571
        }
5572
5573
        return 'single';
5574
    }
5575
5576
    /**
5577
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5578
     *
5579
     * @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...
5580
     *
5581
     * @return bool
5582
     *
5583
     * @author ndiechburg <[email protected]>
5584
     */
5585
    public function set_attempt_mode($mode)
5586
    {
5587
        switch ($mode) {
5588
            case 'seriousgame':
5589
                $sg_mode = 1;
5590
                $prevent_reinit = 1;
5591
                break;
5592
            case 'single':
5593
                $sg_mode = 0;
5594
                $prevent_reinit = 1;
5595
                break;
5596
            case 'multiple':
5597
                $sg_mode = 0;
5598
                $prevent_reinit = 0;
5599
                break;
5600
            default:
5601
                $sg_mode = 0;
5602
                $prevent_reinit = 0;
5603
                break;
5604
        }
5605
        $this->prevent_reinit = $prevent_reinit;
5606
        $this->seriousgame_mode = $sg_mode;
5607
        $table = Database::get_course_table(TABLE_LP_MAIN);
5608
        $sql = "UPDATE $table SET
5609
                prevent_reinit = $prevent_reinit ,
5610
                seriousgame_mode = $sg_mode
5611
                WHERE iid = ".$this->get_id();
5612
        $res = Database::query($sql);
5613
        if ($res) {
5614
            return true;
5615
        } else {
5616
            return false;
5617
        }
5618
    }
5619
5620
    /**
5621
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5622
     *
5623
     * @author ndiechburg <[email protected]>
5624
     */
5625
    public function switch_attempt_mode()
5626
    {
5627
        $mode = $this->get_attempt_mode();
5628
        switch ($mode) {
5629
            case 'single':
5630
                $next_mode = 'multiple';
5631
                break;
5632
            case 'multiple':
5633
                $next_mode = 'seriousgame';
5634
                break;
5635
            case 'seriousgame':
5636
            default:
5637
                $next_mode = 'single';
5638
                break;
5639
        }
5640
        $this->set_attempt_mode($next_mode);
5641
    }
5642
5643
    /**
5644
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5645
     * but possibility to do again a completed item.
5646
     *
5647
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5648
     *
5649
     * @author ndiechburg <[email protected]>
5650
     */
5651
    public function set_seriousgame_mode()
5652
    {
5653
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5654
        $sql = "SELECT * FROM $lp_table
5655
                WHERE iid = ".$this->get_id();
5656
        $res = Database::query($sql);
5657
        if (Database::num_rows($res) > 0) {
5658
            $row = Database::fetch_array($res);
5659
            $force = $row['seriousgame_mode'];
5660
            if ($force == 1) {
5661
                $force = 0;
5662
            } elseif ($force == 0) {
5663
                $force = 1;
5664
            }
5665
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5666
			        WHERE iid = ".$this->get_id();
5667
            Database::query($sql);
5668
            $this->seriousgame_mode = $force;
5669
5670
            return $force;
5671
        }
5672
5673
        return -1;
5674
    }
5675
5676
    /**
5677
     * Updates the "scorm_debug" value that shows or hide the debug window.
5678
     *
5679
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5680
     */
5681
    public function update_scorm_debug()
5682
    {
5683
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5684
        $sql = "SELECT * FROM $lp_table
5685
                WHERE iid = ".$this->get_id();
5686
        $res = Database::query($sql);
5687
        if (Database::num_rows($res) > 0) {
5688
            $row = Database::fetch_array($res);
5689
            $force = $row['debug'];
5690
            if ($force == 1) {
5691
                $force = 0;
5692
            } elseif ($force == 0) {
5693
                $force = 1;
5694
            }
5695
            $sql = "UPDATE $lp_table SET debug = $force
5696
                    WHERE iid = ".$this->get_id();
5697
            Database::query($sql);
5698
            $this->scorm_debug = $force;
5699
5700
            return $force;
5701
        }
5702
5703
        return -1;
5704
    }
5705
5706
    /**
5707
     * Function that makes a call to the function sort_tree_array and create_tree_array.
5708
     *
5709
     * @author Kevin Van Den Haute
5710
     *
5711
     * @param  array
5712
     */
5713
    public function tree_array($array)
5714
    {
5715
        $array = $this->sort_tree_array($array);
5716
        $this->create_tree_array($array);
5717
    }
5718
5719
    /**
5720
     * Creates an array with the elements of the learning path tree in it.
5721
     *
5722
     * @author Kevin Van Den Haute
5723
     *
5724
     * @param array $array
5725
     * @param int   $parent
5726
     * @param int   $depth
5727
     * @param array $tmp
5728
     */
5729
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
5730
    {
5731
        if (is_array($array)) {
5732
            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...
5733
                if ($array[$i]['parent_item_id'] == $parent) {
5734
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
5735
                        $tmp[] = $array[$i]['parent_item_id'];
5736
                        $depth++;
5737
                    }
5738
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
5739
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
5740
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
5741
5742
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
5743
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
5744
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
5745
                    $this->arrMenu[] = [
5746
                        'id' => $array[$i]['id'],
5747
                        'ref' => $ref,
5748
                        'item_type' => $array[$i]['item_type'],
5749
                        'title' => $array[$i]['title'],
5750
                        'title_raw' => $array[$i]['title_raw'],
5751
                        'path' => $path,
5752
                        'description' => $array[$i]['description'],
5753
                        'parent_item_id' => $array[$i]['parent_item_id'],
5754
                        'previous_item_id' => $array[$i]['previous_item_id'],
5755
                        'next_item_id' => $array[$i]['next_item_id'],
5756
                        'min_score' => $array[$i]['min_score'],
5757
                        'max_score' => $array[$i]['max_score'],
5758
                        'mastery_score' => $array[$i]['mastery_score'],
5759
                        'display_order' => $array[$i]['display_order'],
5760
                        'prerequisite' => $preq,
5761
                        'depth' => $depth,
5762
                        'audio' => $audio,
5763
                        'prerequisite_min_score' => $prerequisiteMinScore,
5764
                        'prerequisite_max_score' => $prerequisiteMaxScore,
5765
                    ];
5766
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
5767
                }
5768
            }
5769
        }
5770
    }
5771
5772
    /**
5773
     * Sorts a multi dimensional array by parent id and display order.
5774
     *
5775
     * @author Kevin Van Den Haute
5776
     *
5777
     * @param array $array (array with al the learning path items in it)
5778
     *
5779
     * @return array
5780
     */
5781
    public function sort_tree_array($array)
5782
    {
5783
        foreach ($array as $key => $row) {
5784
            $parent[$key] = $row['parent_item_id'];
5785
            $position[$key] = $row['display_order'];
5786
        }
5787
5788
        if (count($array) > 0) {
5789
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
5790
        }
5791
5792
        return $array;
5793
    }
5794
5795
    /**
5796
     * Function that creates a html list of learning path items so that we can add audio files to them.
5797
     *
5798
     * @author Kevin Van Den Haute
5799
     *
5800
     * @return string
5801
     */
5802
    public function overview()
5803
    {
5804
        $return = '';
5805
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
5806
5807
        // we need to start a form when we want to update all the mp3 files
5808
        if ($update_audio == 'true') {
5809
            $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">';
5810
        }
5811
        $return .= '<div id="message"></div>';
5812
        if (count($this->items) == 0) {
5813
            $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');
5814
        } else {
5815
            $return_audio = '<table class="data_table">';
5816
            $return_audio .= '<tr>';
5817
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
5818
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
5819
            $return_audio .= '</tr>';
5820
5821
            if ($update_audio != 'true') {
5822
                $return .= '<div class="col-md-12">';
5823
                $return .= self::return_new_tree($update_audio);
5824
                $return .= '</div>';
5825
                $return .= Display::div(
5826
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
5827
                    ['style' => 'float:left; margin-top:15px;width:100%']
5828
                );
5829
            } else {
5830
                $return_audio .= self::return_new_tree($update_audio);
5831
                $return .= $return_audio.'</table>';
5832
            }
5833
5834
            // We need to close the form when we are updating the mp3 files.
5835
            if ($update_audio == 'true') {
5836
                $return .= '<div class="footer-audio">';
5837
                $return .= Display::button(
5838
                    'save_audio',
5839
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('Save audio and organization'),
5840
                    ['class' => 'btn btn-primary', 'type' => 'submit']
5841
                );
5842
                $return .= '</div>';
5843
            }
5844
        }
5845
5846
        // We need to close the form when we are updating the mp3 files.
5847
        if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
5848
            $return .= '</form>';
5849
        }
5850
5851
        return $return;
5852
    }
5853
5854
    /**
5855
     * @param string $update_audio
5856
     *
5857
     * @return array
5858
     */
5859
    public function processBuildMenuElements($update_audio = 'false')
5860
    {
5861
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
5862
        $arrLP = $this->getItemsForForm();
5863
5864
        $this->tree_array($arrLP);
5865
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
5866
        unset($this->arrMenu);
5867
        $default_data = null;
5868
        $default_content = null;
5869
        $elements = [];
5870
        $return_audio = null;
5871
        $iconPath = api_get_path(SYS_CODE_PATH).'img/';
5872
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
5873
        $countItems = count($arrLP);
5874
5875
        $upIcon = Display::return_icon(
5876
            'up.png',
5877
            get_lang('Up'),
5878
            [],
5879
            ICON_SIZE_TINY
5880
        );
5881
5882
        $disableUpIcon = Display::return_icon(
5883
            'up_na.png',
5884
            get_lang('Up'),
5885
            [],
5886
            ICON_SIZE_TINY
5887
        );
5888
5889
        $downIcon = Display::return_icon(
5890
            'down.png',
5891
            get_lang('Down'),
5892
            [],
5893
            ICON_SIZE_TINY
5894
        );
5895
5896
        $disableDownIcon = Display::return_icon(
5897
            'down_na.png',
5898
            get_lang('Down'),
5899
            [],
5900
            ICON_SIZE_TINY
5901
        );
5902
5903
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
5904
5905
        $pluginCalendar = api_get_plugin_setting('learning_calendar', 'enabled') === 'true';
5906
        $plugin = null;
5907
        if ($pluginCalendar) {
5908
            $plugin = LearningCalendarPlugin::create();
5909
        }
5910
5911
        for ($i = 0; $i < $countItems; $i++) {
5912
            $parent_id = $arrLP[$i]['parent_item_id'];
5913
            $title = $arrLP[$i]['title'];
5914
            $title_cut = $arrLP[$i]['title_raw'];
5915
            if ($show === false) {
5916
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
5917
            }
5918
            // Link for the documents
5919
            if ($arrLP[$i]['item_type'] === 'document' || $arrLP[$i]['item_type'] == TOOL_READOUT_TEXT) {
5920
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5921
                $title_cut = Display::url(
5922
                    $title_cut,
5923
                    $url,
5924
                    [
5925
                        'class' => 'ajax moved',
5926
                        'data-title' => $title,
5927
                        'title' => $title,
5928
                    ]
5929
                );
5930
            }
5931
5932
            // Detect if type is FINAL_ITEM to set path_id to SESSION
5933
            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
5934
                Session::write('pathItem', $arrLP[$i]['path']);
5935
            }
5936
5937
            $oddClass = 'row_even';
5938
            if (($i % 2) == 0) {
5939
                $oddClass = 'row_odd';
5940
            }
5941
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
5942
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
5943
5944
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
5945
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
5946
            } else {
5947
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
5948
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
5949
                } else {
5950
                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
5951
                        $icon = Display::return_icon('certificate.png');
5952
                    } else {
5953
                        $icon = Display::return_icon('folder_document.png');
5954
                    }
5955
                }
5956
            }
5957
5958
            // The audio column.
5959
            $return_audio .= '<td align="left" style="padding-left:10px;">';
5960
            $audio = '';
5961
            if (!$update_audio || $update_audio != 'true') {
5962
                if (empty($arrLP[$i]['audio'])) {
5963
                    $audio .= '';
5964
                }
5965
            } else {
5966
                $types = self::getChapterTypes();
5967
                if (!in_array($arrLP[$i]['item_type'], $types)) {
5968
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
5969
                    if (!empty($arrLP[$i]['audio'])) {
5970
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
5971
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('Remove audio');
5972
                    }
5973
                }
5974
            }
5975
5976
            $return_audio .= Display::span($icon.' '.$title).
5977
                Display::tag(
5978
                    'td',
5979
                    $audio,
5980
                    ['style' => '']
5981
                );
5982
            $return_audio .= '</td>';
5983
            $move_icon = '';
5984
            $move_item_icon = '';
5985
            $edit_icon = '';
5986
            $delete_icon = '';
5987
            $audio_icon = '';
5988
            $prerequisities_icon = '';
5989
            $forumIcon = '';
5990
            $previewIcon = '';
5991
            $pluginCalendarIcon = '';
5992
            $orderIcons = '';
5993
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
5994
5995
            if ($is_allowed_to_edit) {
5996
                if (!$update_audio || $update_audio != 'true') {
5997
                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
5998
                        $move_icon .= '<a class="moved" href="#">';
5999
                        $move_icon .= Display::return_icon(
6000
                            'move_everywhere.png',
6001
                            get_lang('Move'),
6002
                            [],
6003
                            ICON_SIZE_TINY
6004
                        );
6005
                        $move_icon .= '</a>';
6006
                    }
6007
                }
6008
6009
                // No edit for this item types
6010
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6011
                    if ($arrLP[$i]['item_type'] != 'dir') {
6012
                        $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">';
6013
                        $edit_icon .= Display::return_icon(
6014
                            'edit.png',
6015
                            get_lang('Edit section description/name'),
6016
                            [],
6017
                            ICON_SIZE_TINY
6018
                        );
6019
                        $edit_icon .= '</a>';
6020
6021
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6022
                            $forumThread = null;
6023
                            if (isset($this->items[$arrLP[$i]['id']])) {
6024
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6025
                                    $this->course_int_id,
6026
                                    $this->lp_session_id
6027
                                );
6028
                            }
6029
                            if ($forumThread) {
6030
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6031
                                        'action' => 'dissociate_forum',
6032
                                        'id' => $arrLP[$i]['id'],
6033
                                        'lp_id' => $this->lp_id,
6034
                                    ]);
6035
                                $forumIcon = Display::url(
6036
                                    Display::return_icon(
6037
                                        'forum.png',
6038
                                        get_lang('Dissociate the forum of this learning path item'),
6039
                                        [],
6040
                                        ICON_SIZE_TINY
6041
                                    ),
6042
                                    $forumIconUrl,
6043
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6044
                                );
6045
                            } else {
6046
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6047
                                        'action' => 'create_forum',
6048
                                        'id' => $arrLP[$i]['id'],
6049
                                        'lp_id' => $this->lp_id,
6050
                                    ]);
6051
                                $forumIcon = Display::url(
6052
                                    Display::return_icon(
6053
                                        'forum.png',
6054
                                        get_lang('Associate a forum to this learning path item'),
6055
                                        [],
6056
                                        ICON_SIZE_TINY
6057
                                    ),
6058
                                    $forumIconUrl,
6059
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6060
                                );
6061
                            }
6062
                        }
6063
                    } else {
6064
                        $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">';
6065
                        $edit_icon .= Display::return_icon(
6066
                            'edit.png',
6067
                            get_lang('Edit section description/name'),
6068
                            [],
6069
                            ICON_SIZE_TINY
6070
                        );
6071
                        $edit_icon .= '</a>';
6072
                    }
6073
                } else {
6074
                    if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6075
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6076
                        $edit_icon .= Display::return_icon(
6077
                            'edit.png',
6078
                            get_lang('Edit'),
6079
                            [],
6080
                            ICON_SIZE_TINY
6081
                        );
6082
                        $edit_icon .= '</a>';
6083
                    }
6084
                }
6085
6086
                if ($pluginCalendar) {
6087
                    $pluginLink = $pluginUrl.
6088
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6089
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('1 day'), [], ICON_SIZE_TINY);
6090
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6091
                    if ($itemInfo && $itemInfo['value'] == 1) {
6092
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('1 day'), [], ICON_SIZE_TINY);
6093
                    }
6094
                    $pluginCalendarIcon = Display::url(
6095
                        $iconCalendar,
6096
                        $pluginLink,
6097
                        ['class' => 'btn btn-default']
6098
                    );
6099
                }
6100
6101
                if ($arrLP[$i]['item_type'] != 'final_item') {
6102
                    $orderIcons = Display::url(
6103
                        $upIcon,
6104
                        'javascript:void(0)',
6105
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
6106
                    );
6107
                    $orderIcons .= Display::url(
6108
                        $downIcon,
6109
                        'javascript:void(0)',
6110
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
6111
                    );
6112
                }
6113
6114
                $delete_icon .= ' <a
6115
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
6116
                    onclick="return confirmation(\''.addslashes($title).'\');"
6117
                    class="btn btn-default">';
6118
                $delete_icon .= Display::return_icon(
6119
                    'delete.png',
6120
                    get_lang('Delete section'),
6121
                    [],
6122
                    ICON_SIZE_TINY
6123
                );
6124
                $delete_icon .= '</a>';
6125
6126
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6127
                $previewImage = Display::return_icon(
6128
                    'preview_view.png',
6129
                    get_lang('Preview'),
6130
                    [],
6131
                    ICON_SIZE_TINY
6132
                );
6133
6134
                switch ($arrLP[$i]['item_type']) {
6135
                    case TOOL_DOCUMENT:
6136
                    case TOOL_LP_FINAL_ITEM:
6137
                    case TOOL_READOUT_TEXT:
6138
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6139
                        $previewIcon = Display::url(
6140
                            $previewImage,
6141
                            $urlPreviewLink,
6142
                            [
6143
                                'target' => '_blank',
6144
                                'class' => 'btn btn-default',
6145
                                'data-title' => $arrLP[$i]['title'],
6146
                                'title' => $arrLP[$i]['title'],
6147
                            ]
6148
                        );
6149
                        break;
6150
                    case TOOL_THREAD:
6151
                    case TOOL_FORUM:
6152
                    case TOOL_QUIZ:
6153
                    case TOOL_STUDENTPUBLICATION:
6154
                    case TOOL_LP_FINAL_ITEM:
6155
                    case TOOL_LINK:
6156
                        $class = 'btn btn-default';
6157
                        $target = '_blank';
6158
                        $link = self::rl_get_resource_link_for_learnpath(
6159
                            $this->course_int_id,
6160
                            $this->lp_id,
6161
                            $arrLP[$i]['id'],
6162
                            0
6163
                        );
6164
                        $previewIcon = Display::url(
6165
                            $previewImage,
6166
                            $link,
6167
                            [
6168
                                'class' => $class,
6169
                                'data-title' => $arrLP[$i]['title'],
6170
                                'title' => $arrLP[$i]['title'],
6171
                                'target' => $target,
6172
                            ]
6173
                        );
6174
                        break;
6175
                    default:
6176
                        $previewIcon = Display::url(
6177
                            $previewImage,
6178
                            $url.'&action=view_item',
6179
                            ['class' => 'btn btn-default', 'target' => '_blank']
6180
                        );
6181
                        break;
6182
                }
6183
6184
                if ($arrLP[$i]['item_type'] != 'dir') {
6185
                    $prerequisities_icon = Display::url(
6186
                        Display::return_icon(
6187
                            'accept.png',
6188
                            get_lang('Prerequisites'),
6189
                            [],
6190
                            ICON_SIZE_TINY
6191
                        ),
6192
                        $url.'&action=edit_item_prereq',
6193
                        ['class' => 'btn btn-default']
6194
                    );
6195
                    if ($arrLP[$i]['item_type'] != 'final_item') {
6196
                        $move_item_icon = Display::url(
6197
                            Display::return_icon(
6198
                                'move.png',
6199
                                get_lang('Move'),
6200
                                [],
6201
                                ICON_SIZE_TINY
6202
                            ),
6203
                            $url.'&action=move_item',
6204
                            ['class' => 'btn btn-default']
6205
                        );
6206
                    }
6207
                    $audio_icon = Display::url(
6208
                        Display::return_icon(
6209
                            'audio.png',
6210
                            get_lang('Upload'),
6211
                            [],
6212
                            ICON_SIZE_TINY
6213
                        ),
6214
                        $url.'&action=add_audio',
6215
                        ['class' => 'btn btn-default']
6216
                    );
6217
                }
6218
            }
6219
            if ($update_audio != 'true') {
6220
                $row = $move_icon.' '.$icon.
6221
                    Display::span($title_cut).
6222
                    Display::tag(
6223
                        'div',
6224
                        "<div class=\"btn-group btn-group-xs\">
6225
                                    $previewIcon
6226
                                    $audio
6227
                                    $edit_icon
6228
                                    $pluginCalendarIcon
6229
                                    $forumIcon
6230
                                    $prerequisities_icon
6231
                                    $move_item_icon
6232
                                    $audio_icon
6233
                                    $orderIcons
6234
                                    $delete_icon
6235
                                </div>",
6236
                        ['class' => 'btn-toolbar button_actions']
6237
                    );
6238
            } else {
6239
                $row =
6240
                    Display::span($title.$icon).
6241
                    Display::span($audio, ['class' => 'button_actions']);
6242
            }
6243
6244
            $default_data[$arrLP[$i]['id']] = $row;
6245
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6246
6247
            if (empty($parent_id)) {
6248
                $elements[$arrLP[$i]['id']]['data'] = $row;
6249
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6250
            } else {
6251
                $parent_arrays = [];
6252
                if ($arrLP[$i]['depth'] > 1) {
6253
                    // Getting list of parents
6254
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6255
                        foreach ($arrLP as $item) {
6256
                            if ($item['id'] == $parent_id) {
6257
                                if ($item['parent_item_id'] == 0) {
6258
                                    $parent_id = $item['id'];
6259
                                    break;
6260
                                } else {
6261
                                    $parent_id = $item['parent_item_id'];
6262
                                    if (empty($parent_arrays)) {
6263
                                        $parent_arrays[] = intval($item['id']);
6264
                                    }
6265
                                    $parent_arrays[] = $parent_id;
6266
                                    break;
6267
                                }
6268
                            }
6269
                        }
6270
                    }
6271
                }
6272
6273
                if (!empty($parent_arrays)) {
6274
                    $parent_arrays = array_reverse($parent_arrays);
6275
                    $val = '$elements';
6276
                    $x = 0;
6277
                    foreach ($parent_arrays as $item) {
6278
                        if ($x != count($parent_arrays) - 1) {
6279
                            $val .= '["'.$item.'"]["children"]';
6280
                        } else {
6281
                            $val .= '["'.$item.'"]["children"]';
6282
                        }
6283
                        $x++;
6284
                    }
6285
                    $val .= "";
6286
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6287
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6288
                } else {
6289
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6290
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6291
                }
6292
            }
6293
        }
6294
6295
        return [
6296
            'elements' => $elements,
6297
            'default_data' => $default_data,
6298
            'default_content' => $default_content,
6299
            'return_audio' => $return_audio,
6300
        ];
6301
    }
6302
6303
    /**
6304
     * @param string $updateAudio true/false strings
6305
     *
6306
     * @return string
6307
     */
6308
    public function returnLpItemList($updateAudio)
6309
    {
6310
        $result = $this->processBuildMenuElements($updateAudio);
6311
6312
        $html = self::print_recursive(
6313
            $result['elements'],
6314
            $result['default_data'],
6315
            $result['default_content']
6316
        );
6317
6318
        if (!empty($html)) {
6319
            $html .= Display::return_message(get_lang('Drag and drop an element here'));
6320
        }
6321
6322
        return $html;
6323
    }
6324
6325
    /**
6326
     * @param string $update_audio
6327
     * @param bool   $drop_element_here
6328
     *
6329
     * @return string
6330
     */
6331
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6332
    {
6333
        $result = $this->processBuildMenuElements($update_audio);
6334
6335
        $list = '<ul id="lp_item_list">';
6336
        $tree = $this->print_recursive(
6337
            $result['elements'],
6338
            $result['default_data'],
6339
            $result['default_content']
6340
        );
6341
6342
        if (!empty($tree)) {
6343
            $list .= $tree;
6344
        } else {
6345
            if ($drop_element_here) {
6346
                $list .= Display::return_message(get_lang('Drag and drop an element here'));
6347
            }
6348
        }
6349
        $list .= '</ul>';
6350
6351
        $return = Display::panelCollapse(
6352
            $this->name,
6353
            $list,
6354
            'scorm-list',
6355
            null,
6356
            'scorm-list-accordion',
6357
            'scorm-list-collapse'
6358
        );
6359
6360
        if ($update_audio === 'true') {
6361
            $return = $result['return_audio'];
6362
        }
6363
6364
        return $return;
6365
    }
6366
6367
    /**
6368
     * @param array $elements
6369
     * @param array $default_data
6370
     * @param array $default_content
6371
     *
6372
     * @return string
6373
     */
6374
    public function print_recursive($elements, $default_data, $default_content)
6375
    {
6376
        $return = '';
6377
        foreach ($elements as $key => $item) {
6378
            if (isset($item['load_data']) || empty($item['data'])) {
6379
                $item['data'] = $default_data[$item['load_data']];
6380
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6381
            }
6382
            $sub_list = '';
6383
            if (isset($item['type']) && $item['type'] === 'dir') {
6384
                // empty value
6385
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6386
            }
6387
            if (empty($item['children'])) {
6388
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6389
                $active = null;
6390
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6391
                    $active = 'active';
6392
                }
6393
                $return .= Display::tag(
6394
                    'li',
6395
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6396
                    ['id' => $key, 'class' => 'record li_container']
6397
                );
6398
            } else {
6399
                // Sections
6400
                $data = '';
6401
                if (isset($item['children'])) {
6402
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6403
                }
6404
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6405
                $return .= Display::tag(
6406
                    'li',
6407
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6408
                    ['id' => $key, 'class' => 'record li_container']
6409
                );
6410
            }
6411
        }
6412
6413
        return $return;
6414
    }
6415
6416
    /**
6417
     * This function builds the action menu.
6418
     *
6419
     * @param bool $returnContent          Optional
6420
     * @param bool $showRequirementButtons Optional. Allow show the requirements button
6421
     * @param bool $isConfigPage           Optional. If is the config page, show the edit button
6422
     * @param bool $allowExpand            Optional. Allow show the expand/contract button
6423
     *
6424
     * @return string
6425
     */
6426
    public function build_action_menu(
6427
        $returnContent = false,
6428
        $showRequirementButtons = true,
6429
        $isConfigPage = false,
6430
        $allowExpand = true
6431
    ) {
6432
        $actionsRight = '';
6433
        $actionsLeft = Display::url(
6434
            Display::return_icon(
6435
                'back.png',
6436
                get_lang('Back to learning paths'),
6437
                '',
6438
                ICON_SIZE_MEDIUM
6439
            ),
6440
            'lp_controller.php?'.api_get_cidreq()
6441
        );
6442
        $actionsLeft .= Display::url(
6443
            Display::return_icon(
6444
                'preview_view.png',
6445
                get_lang('Preview'),
6446
                '',
6447
                ICON_SIZE_MEDIUM
6448
            ),
6449
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6450
                'action' => 'view',
6451
                'lp_id' => $this->lp_id,
6452
                'isStudentView' => 'true',
6453
            ])
6454
        );
6455
6456
        $actionsLeft .= Display::url(
6457
            Display::return_icon(
6458
                'upload_audio.png',
6459
                get_lang('Add audio'),
6460
                '',
6461
                ICON_SIZE_MEDIUM
6462
            ),
6463
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6464
                'action' => 'admin_view',
6465
                'lp_id' => $this->lp_id,
6466
                'updateaudio' => 'true',
6467
            ])
6468
        );
6469
6470
        $subscriptionSettings = self::getSubscriptionSettings();
6471
6472
        $request = api_request_uri();
6473
        if (strpos($request, 'edit') === false) {
6474
            $actionsLeft .= Display::url(
6475
                Display::return_icon(
6476
                    'settings.png',
6477
                    get_lang('Course settings'),
6478
                    '',
6479
                    ICON_SIZE_MEDIUM
6480
                ),
6481
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6482
                    'action' => 'edit',
6483
                    'lp_id' => $this->lp_id,
6484
                ])
6485
            );
6486
        }
6487
6488
        if (strpos($request, 'build') === false && strpos($request, 'add_item') === false) {
6489
            $actionsLeft .= Display::url(
6490
                Display::return_icon(
6491
                    'edit.png',
6492
                    get_lang('Edit'),
6493
                    '',
6494
                    ICON_SIZE_MEDIUM
6495
                ),
6496
                'lp_controller.php?'.http_build_query([
6497
                    'action' => 'build',
6498
                    'lp_id' => $this->lp_id,
6499
                ]).'&'.api_get_cidreq()
6500
            );
6501
        }
6502
6503
        if (strpos(api_get_self(), 'lp_subscribe_users.php') === false) {
6504
            if ($this->subscribeUsers == 1 &&
6505
                $subscriptionSettings['allow_add_users_to_lp']) {
6506
                $actionsLeft .= Display::url(
6507
                    Display::return_icon(
6508
                        'user.png',
6509
                        get_lang('Subscribe users to learning path'),
6510
                        '',
6511
                        ICON_SIZE_MEDIUM
6512
                    ),
6513
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=".$this->lp_id."&".api_get_cidreq()
6514
                );
6515
            }
6516
        }
6517
6518
        if ($allowExpand) {
6519
            $actionsLeft .= Display::url(
6520
                Display::return_icon(
6521
                    'expand.png',
6522
                    get_lang('Expand'),
6523
                    ['id' => 'expand'],
6524
                    ICON_SIZE_MEDIUM
6525
                ).
6526
                Display::return_icon(
6527
                    'contract.png',
6528
                    get_lang('Collapse'),
6529
                    ['id' => 'contract', 'class' => 'hide'],
6530
                    ICON_SIZE_MEDIUM
6531
                ),
6532
                '#',
6533
                ['role' => 'button', 'id' => 'hide_bar_template']
6534
            );
6535
        }
6536
6537
        if ($showRequirementButtons) {
6538
            $buttons = [
6539
                [
6540
                    'title' => get_lang('Set previous step as prerequisite for each step'),
6541
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6542
                        'action' => 'set_previous_step_as_prerequisite',
6543
                        'lp_id' => $this->lp_id,
6544
                    ]),
6545
                ],
6546
                [
6547
                    'title' => get_lang('Clear all prerequisites'),
6548
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6549
                        'action' => 'clear_prerequisites',
6550
                        'lp_id' => $this->lp_id,
6551
                    ]),
6552
                ],
6553
            ];
6554
            $actionsRight = Display::groupButtonWithDropDown(
6555
                get_lang('Prerequisites options'),
6556
                $buttons,
6557
                true
6558
            );
6559
        }
6560
6561
        $toolbar = Display::toolbarAction(
6562
            'actions-lp-controller',
6563
            [$actionsLeft, $actionsRight]
6564
        );
6565
6566
        if ($returnContent) {
6567
            return $toolbar;
6568
        }
6569
6570
        echo $toolbar;
6571
    }
6572
6573
    /**
6574
     * Creates the default learning path folder.
6575
     *
6576
     * @param array $course
6577
     * @param int   $creatorId
6578
     *
6579
     * @return bool
6580
     */
6581
    public static function generate_learning_path_folder($course, $creatorId = 0)
6582
    {
6583
        // Creating learning_path folder
6584
        $dir = 'learning_path';
6585
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6586
        $folder = false;
6587
        $folderData = create_unexisting_directory(
6588
            $course,
6589
            $creatorId,
6590
            0,
6591
            null,
6592
            0,
6593
            '',
6594
            $dir,
6595
            get_lang('Learning paths'),
6596
            0
6597
        );
6598
6599
        if (!empty($folderData)) {
6600
            $folder = true;
6601
        }
6602
6603
        return $folder;
6604
    }
6605
6606
    /**
6607
     * @param array  $course
6608
     * @param string $lp_name
6609
     * @param int    $creatorId
6610
     *
6611
     * @return array
6612
     */
6613
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6614
    {
6615
        $filepath = '';
6616
        $dir = '/learning_path/';
6617
6618
        if (empty($lp_name)) {
6619
            $lp_name = $this->name;
6620
        }
6621
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6622
        $folder = self::generate_learning_path_folder($course, $creatorId);
6623
6624
        // Limits title size
6625
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6626
        $dir = $dir.$title;
6627
6628
        // Creating LP folder
6629
        $documentId = null;
6630
        if ($folder) {
6631
            $folderData = create_unexisting_directory(
6632
                $course,
6633
                $creatorId,
6634
                0,
6635
                0,
6636
                0,
6637
                $filepath,
6638
                $dir,
6639
                $lp_name
6640
            );
6641
            if (!empty($folderData)) {
6642
                $folder = true;
6643
            }
6644
6645
            $documentId = $folderData->getIid();
6646
            $dir = $dir.'/';
6647
            if ($folder) {
6648
                // $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6649
            }
6650
        }
6651
6652
        if (empty($documentId)) {
6653
            $dir = api_remove_trailing_slash($dir);
6654
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6655
        }
6656
6657
        $array = [
6658
            'dir' => $dir,
6659
            'filepath' => $filepath,
6660
            'folder' => $folder,
6661
            'id' => $documentId,
6662
        ];
6663
6664
        return $array;
6665
    }
6666
6667
    /**
6668
     * Create a new document //still needs some finetuning.
6669
     *
6670
     * @param array  $courseInfo
6671
     * @param string $content
6672
     * @param string $title
6673
     * @param string $extension
6674
     * @param int    $parentId
6675
     * @param int    $creatorId  creator id
6676
     *
6677
     * @return int
6678
     */
6679
    public function create_document(
6680
        $courseInfo,
6681
        $content = '',
6682
        $title = '',
6683
        $extension = 'html',
6684
        $parentId = 0,
6685
        $creatorId = 0
6686
    ) {
6687
        if (!empty($courseInfo)) {
6688
            $course_id = $courseInfo['real_id'];
6689
        } else {
6690
            $course_id = api_get_course_int_id();
6691
        }
6692
6693
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6694
        $sessionId = api_get_session_id();
6695
6696
        // Generates folder
6697
        $result = $this->generate_lp_folder($courseInfo);
6698
        $dir = $result['dir'];
6699
6700
        if (empty($parentId) || $parentId == '/') {
6701
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6702
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6703
6704
            if ($parentId === '/') {
6705
                $dir = '/';
6706
            }
6707
6708
            // Please, do not modify this dirname formatting.
6709
            if (strstr($dir, '..')) {
6710
                $dir = '/';
6711
            }
6712
6713
            if (!empty($dir[0]) && $dir[0] == '.') {
6714
                $dir = substr($dir, 1);
6715
            }
6716
            if (!empty($dir[0]) && $dir[0] != '/') {
6717
                $dir = '/'.$dir;
6718
            }
6719
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
6720
                $dir .= '/';
6721
            }
6722
        } else {
6723
            $parentInfo = DocumentManager::get_document_data_by_id(
6724
                $parentId,
6725
                $courseInfo['code']
6726
            );
6727
            if (!empty($parentInfo)) {
6728
                $dir = $parentInfo['path'].'/';
6729
            }
6730
        }
6731
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6732
        // is already escaped twice when it gets here.
6733
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6734
        if (!empty($title)) {
6735
            $title = api_replace_dangerous_char(stripslashes($title));
6736
        } else {
6737
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6738
        }
6739
6740
        $title = disable_dangerous_file($title);
6741
        $filename = $title;
6742
        $content = !empty($content) ? $content : $_POST['content_lp'];
6743
        $tmp_filename = $filename;
6744
        $filename = $tmp_filename.'.'.$extension;
6745
6746
        if ($extension === 'html') {
6747
            $content = stripslashes($content);
6748
            $content = str_replace(
6749
                api_get_path(WEB_COURSE_PATH),
6750
                api_get_path(REL_PATH).'courses/',
6751
                $content
6752
            );
6753
6754
            // Change the path of mp3 to absolute.
6755
            // The first regexp deals with :// urls.
6756
            $content = preg_replace(
6757
                "|(flashvars=\"file=)([^:/]+)/|",
6758
                "$1".api_get_path(
6759
                    REL_COURSE_PATH
6760
                ).$courseInfo['path'].'/document/',
6761
                $content
6762
            );
6763
            // The second regexp deals with audio/ urls.
6764
            $content = preg_replace(
6765
                "|(flashvars=\"file=)([^/]+)/|",
6766
                "$1".api_get_path(
6767
                    REL_COURSE_PATH
6768
                ).$courseInfo['path'].'/document/$2/',
6769
                $content
6770
            );
6771
            // For flv player: To prevent edition problem with firefox,
6772
            // we have to use a strange tip (don't blame me please).
6773
            $content = str_replace(
6774
                '</body>',
6775
                '<style type="text/css">body{}</style></body>',
6776
                $content
6777
            );
6778
        }
6779
6780
        $save_file_path = $dir.$filename;
6781
6782
        $document = DocumentManager::addDocument(
6783
            $courseInfo,
6784
            $save_file_path,
6785
            'file',
6786
            '',
6787
            $tmp_filename,
6788
            '',
6789
            0, //readonly
6790
            true,
6791
            null,
6792
            $sessionId,
6793
            $creatorId,
6794
            false,
6795
            $content,
6796
            $parentId
6797
        );
6798
6799
        $document_id = $document->getIid();
6800
        if ($document_id) {
6801
            $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
6802
            $new_title = $originalTitle;
6803
6804
            if ($new_comment || $new_title) {
6805
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
6806
                $ct = '';
6807
                if ($new_comment) {
6808
                    $ct .= ", comment='".Database::escape_string($new_comment)."'";
6809
                }
6810
                if ($new_title) {
6811
                    $ct .= ", title='".Database::escape_string($new_title)."' ";
6812
                }
6813
6814
                $sql = "UPDATE $tbl_doc SET ".substr($ct, 1)."
6815
                        WHERE c_id = $course_id AND id = $document_id ";
6816
                Database::query($sql);
6817
            }
6818
        }
6819
6820
        return $document_id;
6821
    }
6822
6823
    /**
6824
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
6825
     *
6826
     * @param array $_course array
6827
     */
6828
    public function edit_document($_course)
6829
    {
6830
        $course_id = api_get_course_int_id();
6831
        $urlAppend = '';
6832
        // Please, do not modify this dirname formatting.
6833
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
6834
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
6835
6836
        if (strstr($dir, '..')) {
6837
            $dir = '/';
6838
        }
6839
6840
        if (isset($dir[0]) && $dir[0] == '.') {
6841
            $dir = substr($dir, 1);
6842
        }
6843
6844
        if (isset($dir[0]) && $dir[0] != '/') {
6845
            $dir = '/'.$dir;
6846
        }
6847
6848
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
6849
            $dir .= '/';
6850
        }
6851
6852
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
6853
        if (!is_dir($filepath)) {
6854
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
6855
        }
6856
6857
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
6858
6859
        if (isset($_POST['path']) && !empty($_POST['path'])) {
6860
            $document_id = (int) $_POST['path'];
6861
            $documentInfo = DocumentManager::get_document_data_by_id($document_id, api_get_course_id(), false, null, true);
6862
            if (empty($documentInfo)) {
6863
                // Try with iid
6864
                $table = Database::get_course_table(TABLE_DOCUMENT);
6865
                $sql = "SELECT id, path FROM $table
6866
                        WHERE c_id = $course_id AND iid = $document_id AND path NOT LIKE '%_DELETED_%' ";
6867
                $res_doc = Database::query($sql);
6868
                $row = Database::fetch_array($res_doc);
6869
                if ($row) {
6870
                    $document_id = $row['id'];
6871
                    $documentPath = $row['path'];
6872
                }
6873
            } else {
6874
                $documentPath = $documentInfo['path'];
6875
            }
6876
6877
            $content = stripslashes($_POST['content_lp']);
6878
            $file = $filepath.$documentPath;
6879
6880
            if (!file_exists($file)) {
6881
                return false;
6882
            }
6883
6884
            if ($fp = @fopen($file, 'w')) {
6885
                $content = str_replace(
6886
                    api_get_path(WEB_COURSE_PATH),
6887
                    $urlAppend.api_get_path(REL_COURSE_PATH),
6888
                    $content
6889
                );
6890
                // Change the path of mp3 to absolute.
6891
                // The first regexp deals with :// urls.
6892
                $content = preg_replace(
6893
                    "|(flashvars=\"file=)([^:/]+)/|",
6894
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
6895
                    $content
6896
                );
6897
                // The second regexp deals with audio/ urls.
6898
                $content = preg_replace(
6899
                    "|(flashvars=\"file=)([^:/]+)/|",
6900
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
6901
                    $content
6902
                );
6903
                fputs($fp, $content);
6904
                fclose($fp);
6905
6906
                $sql = "UPDATE $table_doc SET
6907
                            title='".Database::escape_string($_POST['title'])."'
6908
                        WHERE c_id = $course_id AND id = ".$document_id;
6909
                Database::query($sql);
6910
            }
6911
        }
6912
    }
6913
6914
    /**
6915
     * Displays the selected item, with a panel for manipulating the item.
6916
     *
6917
     * @param int    $item_id
6918
     * @param string $msg
6919
     * @param bool   $show_actions
6920
     *
6921
     * @return string
6922
     */
6923
    public function display_item($item_id, $msg = null, $show_actions = true)
6924
    {
6925
        $course_id = api_get_course_int_id();
6926
        $return = '';
6927
        if (is_numeric($item_id)) {
6928
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
6929
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
6930
                    WHERE lp.iid = ".intval($item_id);
6931
            $result = Database::query($sql);
6932
            while ($row = Database::fetch_array($result, 'ASSOC')) {
6933
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
6934
6935
                // Prevents wrong parent selection for document, see Bug#1251.
6936
                if ($row['item_type'] != 'dir') {
6937
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
6938
                }
6939
6940
                if ($show_actions) {
6941
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
6942
                }
6943
                $return .= '<div style="padding:10px;">';
6944
6945
                if ($msg != '') {
6946
                    $return .= $msg;
6947
                }
6948
6949
                $return .= '<h3>'.$row['title'].'</h3>';
6950
6951
                switch ($row['item_type']) {
6952
                    case TOOL_THREAD:
6953
                        $link = $this->rl_get_resource_link_for_learnpath(
6954
                            $course_id,
6955
                            $row['lp_id'],
6956
                            $item_id,
6957
                            0
6958
                        );
6959
                        $return .= Display::url(
6960
                            get_lang('Go to thread'),
6961
                            $link,
6962
                            ['class' => 'btn btn-primary']
6963
                        );
6964
                        break;
6965
                    case TOOL_FORUM:
6966
                        $return .= Display::url(
6967
                            get_lang('Go to the forum'),
6968
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
6969
                            ['class' => 'btn btn-primary']
6970
                        );
6971
                        break;
6972
                    case TOOL_QUIZ:
6973
                        if (!empty($row['path'])) {
6974
                            $exercise = new Exercise();
6975
                            $exercise->read($row['path']);
6976
                            $return .= $exercise->description.'<br />';
6977
                            $return .= Display::url(
6978
                                get_lang('Go to exercise'),
6979
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
6980
                                ['class' => 'btn btn-primary']
6981
                            );
6982
                        }
6983
                        break;
6984
                    case TOOL_LP_FINAL_ITEM:
6985
                        $return .= $this->getSavedFinalItem();
6986
                        break;
6987
                    case TOOL_DOCUMENT:
6988
                    case TOOL_READOUT_TEXT:
6989
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
6990
                        $sql_doc = "SELECT path FROM $tbl_doc
6991
                                    WHERE c_id = $course_id AND iid = ".intval($row['path']);
6992
                        $result = Database::query($sql_doc);
6993
                        $path_file = Database::result($result, 0, 0);
6994
                        $path_parts = pathinfo($path_file);
6995
                        // TODO: Correct the following naive comparisons.
6996
                        if (in_array($path_parts['extension'], [
6997
                            'html',
6998
                            'txt',
6999
                            'png',
7000
                            'jpg',
7001
                            'JPG',
7002
                            'jpeg',
7003
                            'JPEG',
7004
                            'gif',
7005
                            'swf',
7006
                            'pdf',
7007
                            'htm',
7008
                        ])) {
7009
                            $return .= $this->display_document($row['path'], true, true);
7010
                        }
7011
                        break;
7012
                    case TOOL_HOTPOTATOES:
7013
                        $return .= $this->display_document($row['path'], false, true);
7014
                        break;
7015
                }
7016
                $return .= '</div>';
7017
            }
7018
        }
7019
7020
        return $return;
7021
    }
7022
7023
    /**
7024
     * Shows the needed forms for editing a specific item.
7025
     *
7026
     * @param int $item_id
7027
     *
7028
     * @throws Exception
7029
     * @throws HTML_QuickForm_Error
7030
     *
7031
     * @return string
7032
     */
7033
    public function display_edit_item($item_id)
7034
    {
7035
        $course_id = api_get_course_int_id();
7036
        $return = '';
7037
        $item_id = (int) $item_id;
7038
7039
        if (empty($item_id)) {
7040
            return '';
7041
        }
7042
7043
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7044
        $sql = "SELECT * FROM $tbl_lp_item
7045
                WHERE iid = ".$item_id;
7046
        $res = Database::query($sql);
7047
        $row = Database::fetch_array($res);
7048
        switch ($row['item_type']) {
7049
            case 'dir':
7050
            case 'asset':
7051
            case 'sco':
7052
                if (isset($_GET['view']) && $_GET['view'] == 'build') {
7053
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7054
                    $return .= $this->display_item_form(
7055
                        $row['item_type'],
7056
                        get_lang('Edit the current section').' :',
7057
                        'edit',
7058
                        $item_id,
7059
                        $row
7060
                    );
7061
                } else {
7062
                    $return .= $this->display_item_form(
7063
                        $row['item_type'],
7064
                        get_lang('Edit the current section').' :',
7065
                        'edit_item',
7066
                        $item_id,
7067
                        $row
7068
                    );
7069
                }
7070
                break;
7071
            case TOOL_DOCUMENT:
7072
            case TOOL_READOUT_TEXT:
7073
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7074
                $sql = "SELECT lp.*, doc.path as dir
7075
                        FROM $tbl_lp_item as lp
7076
                        LEFT JOIN $tbl_doc as doc
7077
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7078
                        WHERE
7079
                            doc.c_id = $course_id AND
7080
                            lp.iid = ".$item_id;
7081
                $res_step = Database::query($sql);
7082
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7083
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7084
7085
                if ($row['item_type'] === TOOL_DOCUMENT) {
7086
                    $return .= $this->display_document_form('edit', $item_id, $row_step);
7087
                }
7088
7089
                if ($row['item_type'] === TOOL_READOUT_TEXT) {
7090
                    $return .= $this->displayFrmReadOutText('edit', $item_id, $row_step);
7091
                }
7092
                break;
7093
            case TOOL_LINK:
7094
                $linkId = (int) $row['path'];
7095
                if (!empty($linkId)) {
7096
                    $table = Database::get_course_table(TABLE_LINK);
7097
                    $sql = 'SELECT url FROM '.$table.'
7098
                            WHERE c_id = '.$course_id.' AND iid = '.$linkId;
7099
                    $res_link = Database::query($sql);
7100
                    $row_link = Database::fetch_array($res_link);
7101
                    if (empty($row_link)) {
7102
                        // Try with id
7103
                        $sql = 'SELECT url FROM '.$table.'
7104
                                WHERE c_id = '.$course_id.' AND id = '.$linkId;
7105
                        $res_link = Database::query($sql);
7106
                        $row_link = Database::fetch_array($res_link);
7107
                    }
7108
7109
                    if (is_array($row_link)) {
7110
                        $row['url'] = $row_link['url'];
7111
                    }
7112
                }
7113
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7114
                $return .= $this->display_link_form('edit', $item_id, $row);
7115
                break;
7116
            case TOOL_LP_FINAL_ITEM:
7117
                Session::write('finalItem', true);
7118
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7119
                $sql = "SELECT lp.*, doc.path as dir
7120
                        FROM $tbl_lp_item as lp
7121
                        LEFT JOIN $tbl_doc as doc
7122
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7123
                        WHERE
7124
                            doc.c_id = $course_id AND
7125
                            lp.iid = ".$item_id;
7126
                $res_step = Database::query($sql);
7127
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7128
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7129
                $return .= $this->display_document_form('edit', $item_id, $row_step);
7130
                break;
7131
            case TOOL_QUIZ:
7132
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7133
                $return .= $this->display_quiz_form('edit', $item_id, $row);
7134
                break;
7135
            case TOOL_HOTPOTATOES:
7136
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7137
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7138
                break;
7139
            case TOOL_STUDENTPUBLICATION:
7140
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7141
                $return .= $this->display_student_publication_form('edit', $item_id, $row);
7142
                break;
7143
            case TOOL_FORUM:
7144
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7145
                $return .= $this->display_forum_form('edit', $item_id, $row);
7146
                break;
7147
            case TOOL_THREAD:
7148
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7149
                $return .= $this->display_thread_form('edit', $item_id, $row);
7150
                break;
7151
        }
7152
7153
        return $return;
7154
    }
7155
7156
    /**
7157
     * Function that displays a list with al the resources that
7158
     * could be added to the learning path.
7159
     *
7160
     * @throws Exception
7161
     * @throws HTML_QuickForm_Error
7162
     *
7163
     * @return bool
7164
     */
7165
    public function display_resources()
7166
    {
7167
        $course_code = api_get_course_id();
7168
7169
        // Get all the docs.
7170
        $documents = $this->get_documents(true);
7171
7172
        // Get all the exercises.
7173
        $exercises = $this->get_exercises();
7174
7175
        // Get all the links.
7176
        $links = $this->get_links();
7177
7178
        // Get all the student publications.
7179
        $works = $this->get_student_publications();
7180
7181
        // Get all the forums.
7182
        $forums = $this->get_forums(null, $course_code);
7183
7184
        // Get the final item form (see BT#11048) .
7185
        $finish = $this->getFinalItemForm();
7186
7187
        $headers = [
7188
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7189
            Display::return_icon('quiz.png', get_lang('Tests'), [], ICON_SIZE_BIG),
7190
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7191
            Display::return_icon('works.png', get_lang('Assignments'), [], ICON_SIZE_BIG),
7192
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7193
            Display::return_icon('add_learnpath_section.png', get_lang('Add section'), [], ICON_SIZE_BIG),
7194
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7195
        ];
7196
7197
        echo Display::return_message(get_lang('Click on the [Learner view] button to see your learning path'), 'normal');
7198
        $dir = $this->display_item_form('dir', get_lang('EnterDataAdd section'), 'add_item');
7199
7200
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
7201
7202
        echo Display::tabs(
7203
            $headers,
7204
            [
7205
                $documents,
7206
                $exercises,
7207
                $links,
7208
                $works,
7209
                $forums,
7210
                $dir,
7211
                $finish,
7212
            ],
7213
            'resource_tab',
7214
            [],
7215
            [],
7216
            $selected
7217
        );
7218
7219
        return true;
7220
    }
7221
7222
    /**
7223
     * Returns the extension of a document.
7224
     *
7225
     * @param string $filename
7226
     *
7227
     * @return string Extension (part after the last dot)
7228
     */
7229
    public function get_extension($filename)
7230
    {
7231
        $explode = explode('.', $filename);
7232
7233
        return $explode[count($explode) - 1];
7234
    }
7235
7236
    /**
7237
     * Displays a document by id.
7238
     *
7239
     * @param int  $id
7240
     * @param bool $show_title
7241
     * @param bool $iframe
7242
     * @param bool $edit_link
7243
     *
7244
     * @return string
7245
     */
7246
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7247
    {
7248
        $_course = api_get_course_info();
7249
        $course_id = api_get_course_int_id();
7250
        $id = (int) $id;
7251
        $return = '';
7252
        $table = Database::get_course_table(TABLE_DOCUMENT);
7253
        $sql_doc = "SELECT * FROM $table
7254
                    WHERE c_id = $course_id AND iid = $id";
7255
        $res_doc = Database::query($sql_doc);
7256
        $row_doc = Database::fetch_array($res_doc);
7257
7258
        // TODO: Add a path filter.
7259
        if ($iframe) {
7260
            $return .= '<iframe id="learnpath_preview_frame" frameborder="0" height="400" width="100%" scrolling="auto" src="'.api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.str_replace('%2F', '/', urlencode($row_doc['path'])).'?'.api_get_cidreq().'"></iframe>';
7261
        } else {
7262
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7263
        }
7264
7265
        return $return;
7266
    }
7267
7268
    /**
7269
     * Return HTML form to add/edit a quiz.
7270
     *
7271
     * @param string $action     Action (add/edit)
7272
     * @param int    $id         Item ID if already exists
7273
     * @param mixed  $extra_info Extra information (quiz ID if integer)
7274
     *
7275
     * @throws Exception
7276
     *
7277
     * @return string HTML form
7278
     */
7279
    public function display_quiz_form($action = 'add', $id = 0, $extra_info = '')
7280
    {
7281
        $course_id = api_get_course_int_id();
7282
        $id = (int) $id;
7283
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7284
        $item_title = '';
7285
        $item_description = '';
7286
        if ($id != 0 && is_array($extra_info)) {
7287
            $item_title = $extra_info['title'];
7288
            $item_description = $extra_info['description'];
7289
        } elseif (is_numeric($extra_info)) {
7290
            $sql = "SELECT title, description
7291
                    FROM $tbl_quiz
7292
                    WHERE c_id = $course_id AND iid = ".$extra_info;
7293
7294
            $result = Database::query($sql);
7295
            $row = Database::fetch_array($result);
7296
            $item_title = $row['title'];
7297
            $item_description = $row['description'];
7298
        }
7299
        $item_title = Security::remove_XSS($item_title);
7300
        $item_description = Security::remove_XSS($item_description);
7301
7302
        $parent = 0;
7303
        if ($id != 0 && is_array($extra_info)) {
7304
            $parent = $extra_info['parent_item_id'];
7305
        }
7306
7307
        $arrLP = $this->getItemsForForm();
7308
        $this->tree_array($arrLP);
7309
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7310
        unset($this->arrMenu);
7311
7312
        $form = new FormValidator(
7313
            'quiz_form',
7314
            'POST',
7315
            $this->getCurrentBuildingModeURL()
7316
        );
7317
        $defaults = [];
7318
7319
        if ($action === 'add') {
7320
            $legend = get_lang('Adding a test to the course');
7321
        } elseif ($action === 'move') {
7322
            $legend = get_lang('Move the current test');
7323
        } else {
7324
            $legend = get_lang('Edit the current test');
7325
        }
7326
7327
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7328
            $legend .= Display::return_message(get_lang('Warning ! ! !').' ! '.get_lang('Warning ! ! !EditingDocument'));
7329
        }
7330
7331
        $form->addHeader($legend);
7332
7333
        if ($action != 'move') {
7334
            $this->setItemTitle($form);
7335
            $defaults['title'] = $item_title;
7336
        }
7337
7338
        // Select for Parent item, root or chapter
7339
        $selectParent = $form->addSelect(
7340
            'parent',
7341
            get_lang('Parent'),
7342
            [],
7343
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7344
        );
7345
        $selectParent->addOption($this->name, 0);
7346
7347
        $arrHide = [
7348
            $id,
7349
        ];
7350
        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...
7351
            if ($action != 'add') {
7352
                if (
7353
                    ($arrLP[$i]['item_type'] == 'dir') &&
7354
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7355
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7356
                ) {
7357
                    $selectParent->addOption(
7358
                        $arrLP[$i]['title'],
7359
                        $arrLP[$i]['id'],
7360
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7361
                    );
7362
7363
                    if ($parent == $arrLP[$i]['id']) {
7364
                        $selectParent->setSelected($arrLP[$i]['id']);
7365
                    }
7366
                } else {
7367
                    $arrHide[] = $arrLP[$i]['id'];
7368
                }
7369
            } else {
7370
                if ($arrLP[$i]['item_type'] == 'dir') {
7371
                    $selectParent->addOption(
7372
                        $arrLP[$i]['title'],
7373
                        $arrLP[$i]['id'],
7374
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7375
                    );
7376
7377
                    if ($parent == $arrLP[$i]['id']) {
7378
                        $selectParent->setSelected($arrLP[$i]['id']);
7379
                    }
7380
                }
7381
            }
7382
        }
7383
7384
        if (is_array($arrLP)) {
7385
            reset($arrLP);
7386
        }
7387
7388
        $selectPrevious = $form->addSelect(
7389
            'previous',
7390
            get_lang('Position'),
7391
            [],
7392
            ['id' => 'previous']
7393
        );
7394
        $selectPrevious->addOption(get_lang('First position'), 0);
7395
7396
        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...
7397
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7398
                $arrLP[$i]['id'] != $id
7399
            ) {
7400
                $selectPrevious->addOption(
7401
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7402
                    $arrLP[$i]['id']
7403
                );
7404
7405
                if (is_array($extra_info)) {
7406
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7407
                        $selectPrevious->setSelected($arrLP[$i]['id']);
7408
                    }
7409
                } elseif ($action == 'add') {
7410
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7411
                }
7412
            }
7413
        }
7414
7415
        if ($action != 'move') {
7416
            $arrHide = [];
7417
            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...
7418
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7419
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7420
                }
7421
            }
7422
        }
7423
7424
        if ($action === 'add') {
7425
            $form->addButtonSave(get_lang('Add test to course'), 'submit_button');
7426
        } else {
7427
            $form->addButtonSave(get_lang('Edit the current test'), 'submit_button');
7428
        }
7429
7430
        if ($action === 'move') {
7431
            $form->addHidden('title', $item_title);
7432
            $form->addHidden('description', $item_description);
7433
        }
7434
7435
        if (is_numeric($extra_info)) {
7436
            $form->addHidden('path', $extra_info);
7437
        } elseif (is_array($extra_info)) {
7438
            $form->addHidden('path', $extra_info['path']);
7439
        }
7440
7441
        $form->addHidden('type', TOOL_QUIZ);
7442
        $form->addHidden('post_time', time());
7443
        $form->setDefaults($defaults);
7444
7445
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7446
    }
7447
7448
    /**
7449
     * Addition of Hotpotatoes tests.
7450
     *
7451
     * @param string $action
7452
     * @param int    $id         Internal ID of the item
7453
     * @param string $extra_info
7454
     *
7455
     * @return string HTML structure to display the hotpotatoes addition formular
7456
     */
7457
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
7458
    {
7459
        $course_id = api_get_course_int_id();
7460
        $uploadPath = DIR_HOTPOTATOES;
7461
7462
        if ($id != 0 && is_array($extra_info)) {
7463
            $item_title = stripslashes($extra_info['title']);
7464
            $item_description = stripslashes($extra_info['description']);
7465
        } elseif (is_numeric($extra_info)) {
7466
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
7467
7468
            $sql = "SELECT * FROM $TBL_DOCUMENT
7469
                    WHERE
7470
                        c_id = $course_id AND
7471
                        path LIKE '".$uploadPath."/%/%htm%' AND
7472
                        iid = ".(int) $extra_info."
7473
                    ORDER BY iid ASC";
7474
7475
            $res_hot = Database::query($sql);
7476
            $row = Database::fetch_array($res_hot);
7477
7478
            $item_title = $row['title'];
7479
            $item_description = $row['description'];
7480
7481
            if (!empty($row['comment'])) {
7482
                $item_title = $row['comment'];
7483
            }
7484
        } else {
7485
            $item_title = '';
7486
            $item_description = '';
7487
        }
7488
7489
        $parent = 0;
7490
        if ($id != 0 && is_array($extra_info)) {
7491
            $parent = $extra_info['parent_item_id'];
7492
        }
7493
7494
        $arrLP = $this->getItemsForForm();
7495
        $legend = '<legend>';
7496
        if ($action == 'add') {
7497
            $legend .= get_lang('Adding a test to the course');
7498
        } elseif ($action == 'move') {
7499
            $legend .= get_lang('Move the current test');
7500
        } else {
7501
            $legend .= get_lang('Edit the current test');
7502
        }
7503
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7504
            $legend .= Display:: return_message(
7505
                get_lang('Warning ! ! !').' ! '.get_lang('Warning ! ! !EditingDocument')
7506
            );
7507
        }
7508
        $legend .= '</legend>';
7509
7510
        $return = '<form method="POST">';
7511
        $return .= $legend;
7512
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
7513
        $return .= '<tr>';
7514
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
7515
        $return .= '<td class="input">';
7516
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
7517
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
7518
        $arrHide = [$id];
7519
7520
        if (count($arrLP) > 0) {
7521
            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...
7522
                if ($action != 'add') {
7523
                    if ($arrLP[$i]['item_type'] == 'dir' &&
7524
                        !in_array($arrLP[$i]['id'], $arrHide) &&
7525
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7526
                    ) {
7527
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7528
                    } else {
7529
                        $arrHide[] = $arrLP[$i]['id'];
7530
                    }
7531
                } else {
7532
                    if ($arrLP[$i]['item_type'] == 'dir') {
7533
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7534
                    }
7535
                }
7536
            }
7537
            reset($arrLP);
7538
        }
7539
7540
        $return .= '</select>';
7541
        $return .= '</td>';
7542
        $return .= '</tr>';
7543
        $return .= '<tr>';
7544
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
7545
        $return .= '<td class="input">';
7546
        $return .= '<select id="previous" name="previous" size="1">';
7547
        $return .= '<option class="top" value="0">'.get_lang('First position').'</option>';
7548
7549
        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...
7550
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7551
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7552
                    $selected = 'selected="selected" ';
7553
                } elseif ($action == 'add') {
7554
                    $selected = 'selected="selected" ';
7555
                } else {
7556
                    $selected = '';
7557
                }
7558
7559
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.
7560
                    get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
7561
            }
7562
        }
7563
7564
        $return .= '</select>';
7565
        $return .= '</td>';
7566
        $return .= '</tr>';
7567
7568
        if ($action != 'move') {
7569
            $return .= '<tr>';
7570
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
7571
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
7572
            $return .= '</tr>';
7573
            $id_prerequisite = 0;
7574
            if (is_array($arrLP) && count($arrLP) > 0) {
7575
                foreach ($arrLP as $key => $value) {
7576
                    if ($value['id'] == $id) {
7577
                        $id_prerequisite = $value['prerequisite'];
7578
                        break;
7579
                    }
7580
                }
7581
7582
                $arrHide = [];
7583
                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...
7584
                    if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7585
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7586
                    }
7587
                }
7588
            }
7589
        }
7590
7591
        $return .= '<tr>';
7592
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.
7593
            get_lang('Save hotpotatoes').'</button></td>';
7594
        $return .= '</tr>';
7595
        $return .= '</table>';
7596
7597
        if ($action == 'move') {
7598
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
7599
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
7600
        }
7601
7602
        if (is_numeric($extra_info)) {
7603
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
7604
        } elseif (is_array($extra_info)) {
7605
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
7606
        }
7607
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
7608
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
7609
        $return .= '</form>';
7610
7611
        return $return;
7612
    }
7613
7614
    /**
7615
     * Return the form to display the forum edit/add option.
7616
     *
7617
     * @param string $action
7618
     * @param int    $id         ID of the lp_item if already exists
7619
     * @param string $extra_info
7620
     *
7621
     * @throws Exception
7622
     *
7623
     * @return string HTML form
7624
     */
7625
    public function display_forum_form($action = 'add', $id = 0, $extra_info = '')
7626
    {
7627
        $course_id = api_get_course_int_id();
7628
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
7629
7630
        $item_title = '';
7631
        $item_description = '';
7632
7633
        if ($id != 0 && is_array($extra_info)) {
7634
            $item_title = stripslashes($extra_info['title']);
7635
        } elseif (is_numeric($extra_info)) {
7636
            $sql = "SELECT forum_title as title, forum_comment as comment
7637
                    FROM $tbl_forum
7638
                    WHERE c_id = $course_id AND forum_id = ".$extra_info;
7639
7640
            $result = Database::query($sql);
7641
            $row = Database::fetch_array($result);
7642
7643
            $item_title = $row['title'];
7644
            $item_description = $row['comment'];
7645
        }
7646
        $parent = 0;
7647
        if ($id != 0 && is_array($extra_info)) {
7648
            $parent = $extra_info['parent_item_id'];
7649
        }
7650
        $arrLP = $this->getItemsForForm();
7651
        $this->tree_array($arrLP);
7652
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7653
        unset($this->arrMenu);
7654
7655
        if ($action == 'add') {
7656
            $legend = get_lang('Adding a forum to the course');
7657
        } elseif ($action == 'move') {
7658
            $legend = get_lang('Move the current forum');
7659
        } else {
7660
            $legend = get_lang('Edit the current forum');
7661
        }
7662
7663
        $form = new FormValidator(
7664
            'forum_form',
7665
            'POST',
7666
            $this->getCurrentBuildingModeURL()
7667
        );
7668
        $defaults = [];
7669
7670
        $form->addHeader($legend);
7671
7672
        if ($action != 'move') {
7673
            $this->setItemTitle($form);
7674
            $defaults['title'] = $item_title;
7675
        }
7676
7677
        $selectParent = $form->addSelect(
7678
            'parent',
7679
            get_lang('Parent'),
7680
            [],
7681
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
7682
        );
7683
        $selectParent->addOption($this->name, 0);
7684
        $arrHide = [
7685
            $id,
7686
        ];
7687
        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...
7688
            if ($action != 'add') {
7689
                if ($arrLP[$i]['item_type'] == 'dir' &&
7690
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7691
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7692
                ) {
7693
                    $selectParent->addOption(
7694
                        $arrLP[$i]['title'],
7695
                        $arrLP[$i]['id'],
7696
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7697
                    );
7698
7699
                    if ($parent == $arrLP[$i]['id']) {
7700
                        $selectParent->setSelected($arrLP[$i]['id']);
7701
                    }
7702
                } else {
7703
                    $arrHide[] = $arrLP[$i]['id'];
7704
                }
7705
            } else {
7706
                if ($arrLP[$i]['item_type'] == 'dir') {
7707
                    $selectParent->addOption(
7708
                        $arrLP[$i]['title'],
7709
                        $arrLP[$i]['id'],
7710
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7711
                    );
7712
7713
                    if ($parent == $arrLP[$i]['id']) {
7714
                        $selectParent->setSelected($arrLP[$i]['id']);
7715
                    }
7716
                }
7717
            }
7718
        }
7719
7720
        if (is_array($arrLP)) {
7721
            reset($arrLP);
7722
        }
7723
7724
        $selectPrevious = $form->addSelect(
7725
            'previous',
7726
            get_lang('Position'),
7727
            [],
7728
            ['id' => 'previous', 'class' => 'learnpath_item_form']
7729
        );
7730
        $selectPrevious->addOption(get_lang('First position'), 0);
7731
7732
        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...
7733
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7734
                $arrLP[$i]['id'] != $id
7735
            ) {
7736
                $selectPrevious->addOption(
7737
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7738
                    $arrLP[$i]['id']
7739
                );
7740
7741
                if (isset($extra_info['previous_item_id']) &&
7742
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
7743
                ) {
7744
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7745
                } elseif ($action == 'add') {
7746
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7747
                }
7748
            }
7749
        }
7750
7751
        if ($action != 'move') {
7752
            $id_prerequisite = 0;
7753
            if (is_array($arrLP)) {
7754
                foreach ($arrLP as $key => $value) {
7755
                    if ($value['id'] == $id) {
7756
                        $id_prerequisite = $value['prerequisite'];
7757
                        break;
7758
                    }
7759
                }
7760
            }
7761
7762
            $arrHide = [];
7763
            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...
7764
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7765
                    if (isset($extra_info['previous_item_id']) &&
7766
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
7767
                    ) {
7768
                        $s_selected_position = $arrLP[$i]['id'];
7769
                    } elseif ($action == 'add') {
7770
                        $s_selected_position = 0;
7771
                    }
7772
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7773
                }
7774
            }
7775
        }
7776
7777
        if ($action == 'add') {
7778
            $form->addButtonSave(get_lang('Add forum to course'), 'submit_button');
7779
        } else {
7780
            $form->addButtonSave(get_lang('Edit the current forum'), 'submit_button');
7781
        }
7782
7783
        if ($action == 'move') {
7784
            $form->addHidden('title', $item_title);
7785
            $form->addHidden('description', $item_description);
7786
        }
7787
7788
        if (is_numeric($extra_info)) {
7789
            $form->addHidden('path', $extra_info);
7790
        } elseif (is_array($extra_info)) {
7791
            $form->addHidden('path', $extra_info['path']);
7792
        }
7793
        $form->addHidden('type', TOOL_FORUM);
7794
        $form->addHidden('post_time', time());
7795
        $form->setDefaults($defaults);
7796
7797
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7798
    }
7799
7800
    /**
7801
     * Return HTML form to add/edit forum threads.
7802
     *
7803
     * @param string $action
7804
     * @param int    $id         Item ID if already exists in learning path
7805
     * @param string $extra_info
7806
     *
7807
     * @throws Exception
7808
     *
7809
     * @return string HTML form
7810
     */
7811
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
7812
    {
7813
        $course_id = api_get_course_int_id();
7814
        if (empty($course_id)) {
7815
            return null;
7816
        }
7817
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
7818
7819
        $item_title = '';
7820
        $item_description = '';
7821
        if ($id != 0 && is_array($extra_info)) {
7822
            $item_title = stripslashes($extra_info['title']);
7823
        } elseif (is_numeric($extra_info)) {
7824
            $sql = "SELECT thread_title as title FROM $tbl_forum
7825
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
7826
7827
            $result = Database::query($sql);
7828
            $row = Database::fetch_array($result);
7829
7830
            $item_title = $row['title'];
7831
            $item_description = '';
7832
        }
7833
7834
        $parent = 0;
7835
        if ($id != 0 && is_array($extra_info)) {
7836
            $parent = $extra_info['parent_item_id'];
7837
        }
7838
7839
        $arrLP = $this->getItemsForForm();
7840
        $this->tree_array($arrLP);
7841
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7842
        unset($this->arrMenu);
7843
7844
        $form = new FormValidator(
7845
            'thread_form',
7846
            'POST',
7847
            $this->getCurrentBuildingModeURL()
7848
        );
7849
        $defaults = [];
7850
7851
        if ($action == 'add') {
7852
            $legend = get_lang('Adding a forum to the course');
7853
        } elseif ($action == 'move') {
7854
            $legend = get_lang('Move the current forum');
7855
        } else {
7856
            $legend = get_lang('Edit the current forum');
7857
        }
7858
7859
        $form->addHeader($legend);
7860
        $selectParent = $form->addSelect(
7861
            'parent',
7862
            get_lang('Parent'),
7863
            [],
7864
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7865
        );
7866
        $selectParent->addOption($this->name, 0);
7867
7868
        $arrHide = [
7869
            $id,
7870
        ];
7871
7872
        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...
7873
            if ($action != 'add') {
7874
                if (
7875
                    ($arrLP[$i]['item_type'] == 'dir') &&
7876
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7877
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7878
                ) {
7879
                    $selectParent->addOption(
7880
                        $arrLP[$i]['title'],
7881
                        $arrLP[$i]['id'],
7882
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7883
                    );
7884
7885
                    if ($parent == $arrLP[$i]['id']) {
7886
                        $selectParent->setSelected($arrLP[$i]['id']);
7887
                    }
7888
                } else {
7889
                    $arrHide[] = $arrLP[$i]['id'];
7890
                }
7891
            } else {
7892
                if ($arrLP[$i]['item_type'] == 'dir') {
7893
                    $selectParent->addOption(
7894
                        $arrLP[$i]['title'],
7895
                        $arrLP[$i]['id'],
7896
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7897
                    );
7898
7899
                    if ($parent == $arrLP[$i]['id']) {
7900
                        $selectParent->setSelected($arrLP[$i]['id']);
7901
                    }
7902
                }
7903
            }
7904
        }
7905
7906
        if ($arrLP != null) {
7907
            reset($arrLP);
7908
        }
7909
7910
        $selectPrevious = $form->addSelect(
7911
            'previous',
7912
            get_lang('Position'),
7913
            [],
7914
            ['id' => 'previous']
7915
        );
7916
        $selectPrevious->addOption(get_lang('First position'), 0);
7917
7918
        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...
7919
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7920
                $selectPrevious->addOption(
7921
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7922
                    $arrLP[$i]['id']
7923
                );
7924
7925
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7926
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7927
                } elseif ($action == 'add') {
7928
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7929
                }
7930
            }
7931
        }
7932
7933
        if ($action != 'move') {
7934
            $this->setItemTitle($form);
7935
            $defaults['title'] = $item_title;
7936
7937
            $id_prerequisite = 0;
7938
            if ($arrLP != null) {
7939
                foreach ($arrLP as $key => $value) {
7940
                    if ($value['id'] == $id) {
7941
                        $id_prerequisite = $value['prerequisite'];
7942
                        break;
7943
                    }
7944
                }
7945
            }
7946
7947
            $arrHide = [];
7948
            $s_selected_position = 0;
7949
            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...
7950
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7951
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7952
                        $s_selected_position = $arrLP[$i]['id'];
7953
                    } elseif ($action == 'add') {
7954
                        $s_selected_position = 0;
7955
                    }
7956
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7957
                }
7958
            }
7959
7960
            $selectPrerequisites = $form->addSelect(
7961
                'prerequisites',
7962
                get_lang('Prerequisites'),
7963
                [],
7964
                ['id' => 'prerequisites']
7965
            );
7966
            $selectPrerequisites->addOption(get_lang('No prerequisites'), 0);
7967
7968
            foreach ($arrHide as $key => $value) {
7969
                $selectPrerequisites->addOption($value['value'], $key);
7970
7971
                if ($key == $s_selected_position && $action == 'add') {
7972
                    $selectPrerequisites->setSelected($key);
7973
                } elseif ($key == $id_prerequisite && $action == 'edit') {
7974
                    $selectPrerequisites->setSelected($key);
7975
                }
7976
            }
7977
        }
7978
7979
        $form->addButtonSave(get_lang('Validate'), 'submit_button');
7980
7981
        if ($action == 'move') {
7982
            $form->addHidden('title', $item_title);
7983
            $form->addHidden('description', $item_description);
7984
        }
7985
7986
        if (is_numeric($extra_info)) {
7987
            $form->addHidden('path', $extra_info);
7988
        } elseif (is_array($extra_info)) {
7989
            $form->addHidden('path', $extra_info['path']);
7990
        }
7991
7992
        $form->addHidden('type', TOOL_THREAD);
7993
        $form->addHidden('post_time', time());
7994
        $form->setDefaults($defaults);
7995
7996
        return $form->returnForm();
7997
    }
7998
7999
    /**
8000
     * Return the HTML form to display an item (generally a dir item).
8001
     *
8002
     * @param string $item_type
8003
     * @param string $title
8004
     * @param string $action
8005
     * @param int    $id
8006
     * @param string $extra_info
8007
     *
8008
     * @throws Exception
8009
     * @throws HTML_QuickForm_Error
8010
     *
8011
     * @return string HTML form
8012
     */
8013
    public function display_item_form(
8014
        $item_type,
8015
        $title = '',
8016
        $action = 'add_item',
8017
        $id = 0,
8018
        $extra_info = 'new'
8019
    ) {
8020
        $_course = api_get_course_info();
8021
8022
        global $charset;
8023
8024
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8025
        $item_title = '';
8026
        $item_description = '';
8027
        $item_path_fck = '';
8028
8029
        if ($id != 0 && is_array($extra_info)) {
8030
            $item_title = $extra_info['title'];
8031
            $item_description = $extra_info['description'];
8032
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8033
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8034
        }
8035
        $parent = 0;
8036
        if ($id != 0 && is_array($extra_info)) {
8037
            $parent = $extra_info['parent_item_id'];
8038
        }
8039
8040
        $id = (int) $id;
8041
        $sql = "SELECT * FROM $tbl_lp_item
8042
                WHERE
8043
                    lp_id = ".$this->lp_id." AND
8044
                    iid != $id";
8045
8046
        if ($item_type == 'dir') {
8047
            $sql .= " AND parent_item_id = 0";
8048
        }
8049
8050
        $result = Database::query($sql);
8051
        $arrLP = [];
8052
        while ($row = Database::fetch_array($result)) {
8053
            $arrLP[] = [
8054
                'id' => $row['iid'],
8055
                'item_type' => $row['item_type'],
8056
                'title' => $this->cleanItemTitle($row['title']),
8057
                'title_raw' => $row['title'],
8058
                'path' => $row['path'],
8059
                'description' => $row['description'],
8060
                'parent_item_id' => $row['parent_item_id'],
8061
                'previous_item_id' => $row['previous_item_id'],
8062
                'next_item_id' => $row['next_item_id'],
8063
                'max_score' => $row['max_score'],
8064
                'min_score' => $row['min_score'],
8065
                'mastery_score' => $row['mastery_score'],
8066
                'prerequisite' => $row['prerequisite'],
8067
                'display_order' => $row['display_order'],
8068
            ];
8069
        }
8070
8071
        $this->tree_array($arrLP);
8072
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8073
        unset($this->arrMenu);
8074
8075
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8076
8077
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
8078
        $defaults['title'] = api_html_entity_decode(
8079
            $item_title,
8080
            ENT_QUOTES,
8081
            $charset
8082
        );
8083
        $defaults['description'] = $item_description;
8084
8085
        $form->addHeader($title);
8086
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
8087
        $arrHide[0]['padding'] = 20;
8088
        $charset = api_get_system_encoding();
8089
        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...
8090
            if ($action != 'add') {
8091
                if ($arrLP[$i]['item_type'] === 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8092
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8093
                ) {
8094
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8095
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8096
                    if ($parent == $arrLP[$i]['id']) {
8097
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8098
                    }
8099
                }
8100
            } else {
8101
                if ($arrLP[$i]['item_type'] === 'dir') {
8102
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8103
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8104
                    if ($parent == $arrLP[$i]['id']) {
8105
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8106
                    }
8107
                }
8108
            }
8109
        }
8110
8111
        if ($action != 'move') {
8112
            $this->setItemTitle($form);
8113
        } else {
8114
            $form->addElement('hidden', 'title');
8115
        }
8116
8117
        $parentSelect = $form->addElement(
8118
            'select',
8119
            'parent',
8120
            get_lang('Parent'),
8121
            '',
8122
            [
8123
                'id' => 'idParent',
8124
                'onchange' => 'javascript: load_cbo(this.value);',
8125
            ]
8126
        );
8127
8128
        foreach ($arrHide as $key => $value) {
8129
            $parentSelect->addOption(
8130
                $value['value'],
8131
                $key,
8132
                'style="padding-left:'.$value['padding'].'px;"'
8133
            );
8134
            $lastPosition = $key;
8135
        }
8136
8137
        if (!empty($s_selected_parent)) {
8138
            $parentSelect->setSelected($s_selected_parent);
8139
        }
8140
8141
        if (is_array($arrLP)) {
8142
            reset($arrLP);
8143
        }
8144
8145
        $arrHide = [];
8146
        // POSITION
8147
        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...
8148
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id &&
8149
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
8150
                //this is the same!
8151
                if (isset($extra_info['previous_item_id']) &&
8152
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8153
                ) {
8154
                    $s_selected_position = $arrLP[$i]['id'];
8155
                } elseif ($action == 'add') {
8156
                    $s_selected_position = $arrLP[$i]['id'];
8157
                }
8158
8159
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8160
            }
8161
        }
8162
8163
        $position = $form->addElement(
8164
            'select',
8165
            'previous',
8166
            get_lang('Position'),
8167
            '',
8168
            ['id' => 'previous']
8169
        );
8170
        $padding = isset($value['padding']) ? $value['padding'] : 0;
8171
        $position->addOption(get_lang('First position'), 0, 'style="padding-left:'.$padding.'px;"');
8172
8173
        $lastPosition = null;
8174
        foreach ($arrHide as $key => $value) {
8175
            $position->addOption($value['value'], $key, 'style="padding-left:'.$padding.'px;"');
8176
            $lastPosition = $key;
8177
        }
8178
8179
        if (!empty($s_selected_position)) {
8180
            $position->setSelected($s_selected_position);
8181
        }
8182
8183
        // When new chapter add at the end
8184
        if ($action === 'add_item') {
8185
            $position->setSelected($lastPosition);
8186
        }
8187
8188
        if (is_array($arrLP)) {
8189
            reset($arrLP);
8190
        }
8191
8192
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
8193
8194
        //fix in order to use the tab
8195
        if ($item_type === 'dir') {
8196
            $form->addElement('hidden', 'type', 'dir');
8197
        }
8198
8199
        $extension = null;
8200
        if (!empty($item_path)) {
8201
            $extension = pathinfo($item_path, PATHINFO_EXTENSION);
8202
        }
8203
8204
        //assets can't be modified
8205
        //$item_type == 'asset' ||
8206
        if (($item_type == 'sco') && ($extension == 'html' || $extension == 'htm')) {
8207
            if ($item_type == 'sco') {
8208
                $form->addElement(
8209
                    'html',
8210
                    '<script>alert("'.get_lang('Warning ! ! !WhenEditingScorm').'")</script>'
8211
                );
8212
            }
8213
            $renderer = $form->defaultRenderer();
8214
            $renderer->setElementTemplate(
8215
                '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
8216
                'content_lp'
8217
            );
8218
8219
            $relative_prefix = '';
8220
            $editor_config = [
8221
                'ToolbarSet' => 'LearningPathDocuments',
8222
                'Width' => '100%',
8223
                'Height' => '500',
8224
                'FullPage' => true,
8225
                'CreateDocumentDir' => $relative_prefix,
8226
                'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
8227
                'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
8228
            ];
8229
8230
            $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
8231
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
8232
            $defaults['content_lp'] = file_get_contents($content_path);
8233
        }
8234
8235
        if (!empty($id)) {
8236
            $form->addHidden('id', $id);
8237
        }
8238
8239
        $form->addElement('hidden', 'type', $item_type);
8240
        $form->addElement('hidden', 'post_time', time());
8241
        $form->setDefaults($defaults);
8242
8243
        return $form->returnForm();
8244
    }
8245
8246
    /**
8247
     * @return string
8248
     */
8249
    public function getCurrentBuildingModeURL()
8250
    {
8251
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
8252
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
8253
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
8254
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
8255
8256
        $currentUrl = api_get_self().'?'.api_get_cidreq().
8257
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
8258
8259
        return $currentUrl;
8260
    }
8261
8262
    /**
8263
     * Returns the form to update or create a document.
8264
     *
8265
     * @param string $action     (add/edit)
8266
     * @param int    $id         ID of the lp_item (if already exists)
8267
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8268
     *
8269
     * @throws Exception
8270
     * @throws HTML_QuickForm_Error
8271
     *
8272
     * @return string HTML form
8273
     */
8274
    public function display_document_form($action = 'add', $id = 0, $extra_info = 'new')
8275
    {
8276
        $course_id = api_get_course_int_id();
8277
        $_course = api_get_course_info();
8278
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8279
8280
        $no_display_edit_textarea = false;
8281
        $item_description = '';
8282
        //If action==edit document
8283
        //We don't display the document form if it's not an editable document (html or txt file)
8284
        if ($action === 'edit') {
8285
            if (is_array($extra_info)) {
8286
                $path_parts = pathinfo($extra_info['dir']);
8287
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8288
                    $no_display_edit_textarea = true;
8289
                }
8290
            }
8291
        }
8292
        $no_display_add = false;
8293
8294
        // If action==add an existing document
8295
        // We don't display the document form if it's not an editable document (html or txt file).
8296
        if ($action === 'add') {
8297
            if (is_numeric($extra_info)) {
8298
                $extra_info = (int) $extra_info;
8299
                $sql_doc = "SELECT path FROM $tbl_doc
8300
                            WHERE c_id = $course_id AND iid = ".$extra_info;
8301
                $result = Database::query($sql_doc);
8302
                $path_file = Database::result($result, 0, 0);
8303
                $path_parts = pathinfo($path_file);
8304
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8305
                    $no_display_add = true;
8306
                }
8307
            }
8308
        }
8309
8310
        $item_title = '';
8311
        $item_description = '';
8312
        if ($id != 0 && is_array($extra_info)) {
8313
            $item_title = stripslashes($extra_info['title']);
8314
            $item_description = stripslashes($extra_info['description']);
8315
            if (empty($item_title)) {
8316
                $path_parts = pathinfo($extra_info['path']);
8317
                $item_title = stripslashes($path_parts['filename']);
8318
            }
8319
        } elseif (is_numeric($extra_info)) {
8320
            $sql = "SELECT path, title FROM $tbl_doc
8321
                    WHERE
8322
                        c_id = ".$course_id." AND
8323
                        iid = ".intval($extra_info);
8324
            $result = Database::query($sql);
8325
            $row = Database::fetch_array($result);
8326
            $item_title = $row['title'];
8327
            $item_title = str_replace('_', ' ', $item_title);
8328
            if (empty($item_title)) {
8329
                $path_parts = pathinfo($row['path']);
8330
                $item_title = stripslashes($path_parts['filename']);
8331
            }
8332
        }
8333
8334
        $return = '<legend>';
8335
        $parent = 0;
8336
        if ($id != 0 && is_array($extra_info)) {
8337
            $parent = $extra_info['parent_item_id'];
8338
        }
8339
8340
        $arrLP = $this->getItemsForForm();
8341
        $this->tree_array($arrLP);
8342
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8343
        unset($this->arrMenu);
8344
8345
        if ($action == 'add') {
8346
            $return .= get_lang('Create a new document');
8347
        } elseif ($action == 'move') {
8348
            $return .= get_lang('Move the current document');
8349
        } else {
8350
            $return .= get_lang('Edit the current document');
8351
        }
8352
        $return .= '</legend>';
8353
8354
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
8355
            $return .= Display::return_message(
8356
                '<strong>'.get_lang('Warning ! ! !').' !</strong><br />'.get_lang('Warning ! ! !EditingDocument'),
8357
                false
8358
            );
8359
        }
8360
        $form = new FormValidator(
8361
            'form',
8362
            'POST',
8363
            $this->getCurrentBuildingModeURL(),
8364
            '',
8365
            ['enctype' => 'multipart/form-data']
8366
        );
8367
        $defaults['title'] = Security::remove_XSS($item_title);
8368
        if (empty($item_title)) {
8369
            $defaults['title'] = Security::remove_XSS($item_title);
8370
        }
8371
        $defaults['description'] = $item_description;
8372
        $form->addElement('html', $return);
8373
8374
        if ($action != 'move') {
8375
            $data = $this->generate_lp_folder($_course);
8376
            if ($action != 'edit') {
8377
                $folders = DocumentManager::get_all_document_folders(
8378
                    $_course,
8379
                    0,
8380
                    true
8381
                );
8382
                DocumentManager::build_directory_selector(
8383
                    $folders,
8384
                    '',
8385
                    [],
8386
                    true,
8387
                    $form,
8388
                    'directory_parent_id'
8389
                );
8390
            }
8391
8392
            if (isset($data['id'])) {
8393
                $defaults['directory_parent_id'] = $data['id'];
8394
            }
8395
            $this->setItemTitle($form);
8396
        }
8397
8398
        $arrHide[0]['value'] = $this->name;
8399
        $arrHide[0]['padding'] = 20;
8400
8401
        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...
8402
            if ($action != 'add') {
8403
                if ($arrLP[$i]['item_type'] == 'dir' &&
8404
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8405
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8406
                ) {
8407
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8408
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8409
                }
8410
            } else {
8411
                if ($arrLP[$i]['item_type'] == 'dir') {
8412
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8413
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8414
                }
8415
            }
8416
        }
8417
8418
        $parentSelect = $form->addSelect(
8419
            'parent',
8420
            get_lang('Parent'),
8421
            [],
8422
            [
8423
                'id' => 'idParent',
8424
                'onchange' => 'javascript: load_cbo(this.value);',
8425
            ]
8426
        );
8427
8428
        $my_count = 0;
8429
        foreach ($arrHide as $key => $value) {
8430
            if ($my_count != 0) {
8431
                // The LP name is also the first section and is not in the same charset like the other sections.
8432
                $value['value'] = Security::remove_XSS($value['value']);
8433
                $parentSelect->addOption(
8434
                    $value['value'],
8435
                    $key,
8436
                    'style="padding-left:'.$value['padding'].'px;"'
8437
                );
8438
            } else {
8439
                $value['value'] = Security::remove_XSS($value['value']);
8440
                $parentSelect->addOption(
8441
                    $value['value'],
8442
                    $key,
8443
                    'style="padding-left:'.$value['padding'].'px;"'
8444
                );
8445
            }
8446
            $my_count++;
8447
        }
8448
8449
        if (!empty($id)) {
8450
            $parentSelect->setSelected($parent);
8451
        } else {
8452
            $parent_item_id = Session::read('parent_item_id', 0);
8453
            $parentSelect->setSelected($parent_item_id);
8454
        }
8455
8456
        if (is_array($arrLP)) {
8457
            reset($arrLP);
8458
        }
8459
8460
        $arrHide = [];
8461
        // POSITION
8462
        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...
8463
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
8464
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
8465
            ) {
8466
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8467
            }
8468
        }
8469
8470
        $selectedPosition = isset($extra_info['previous_item_id']) ? $extra_info['previous_item_id'] : 0;
8471
8472
        $position = $form->addSelect(
8473
            'previous',
8474
            get_lang('Position'),
8475
            [],
8476
            ['id' => 'previous']
8477
        );
8478
8479
        $position->addOption(get_lang('First position'), 0);
8480
        foreach ($arrHide as $key => $value) {
8481
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8482
            $position->addOption(
8483
                $value['value'],
8484
                $key,
8485
                'style="padding-left:'.$padding.'px;"'
8486
            );
8487
        }
8488
8489
        $position->setSelected($selectedPosition);
8490
8491
        if (is_array($arrLP)) {
8492
            reset($arrLP);
8493
        }
8494
8495
        if ($action != 'move') {
8496
            $arrHide = [];
8497
            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...
8498
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
8499
                    $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8500
                ) {
8501
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8502
                }
8503
            }
8504
8505
            if (!$no_display_add) {
8506
                $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8507
                $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8508
                if ($extra_info == 'new' || $item_type == TOOL_DOCUMENT ||
8509
                    $item_type == TOOL_LP_FINAL_ITEM || $edit == 'true'
8510
                ) {
8511
                    if (isset($_POST['content'])) {
8512
                        $content = stripslashes($_POST['content']);
8513
                    } elseif (is_array($extra_info)) {
8514
                        //If it's an html document or a text file
8515
                        if (!$no_display_edit_textarea) {
8516
                            $content = $this->display_document(
8517
                                $extra_info['path'],
8518
                                false,
8519
                                false
8520
                            );
8521
                        }
8522
                    } elseif (is_numeric($extra_info)) {
8523
                        $content = $this->display_document(
8524
                            $extra_info,
8525
                            false,
8526
                            false
8527
                        );
8528
                    } else {
8529
                        $content = '';
8530
                    }
8531
8532
                    if (!$no_display_edit_textarea) {
8533
                        // We need to calculate here some specific settings for the online editor.
8534
                        // The calculated settings work for documents in the Documents tool
8535
                        // (on the root or in subfolders).
8536
                        // For documents in native scorm packages it is unclear whether the
8537
                        // online editor should be activated or not.
8538
8539
                        // A new document, it is in the root of the repository.
8540
                        $relative_path = '';
8541
                        $relative_prefix = '';
8542
                        if (is_array($extra_info) && $extra_info != 'new') {
8543
                            // The document already exists. Whe have to determine its relative path towards the repository root.
8544
                            $relative_path = explode('/', $extra_info['dir']);
8545
                            $cnt = count($relative_path) - 2;
8546
                            if ($cnt < 0) {
8547
                                $cnt = 0;
8548
                            }
8549
                            $relative_prefix = str_repeat('../', $cnt);
8550
                            $relative_path = array_slice($relative_path, 1, $cnt);
8551
                            $relative_path = implode('/', $relative_path);
8552
                            if (strlen($relative_path) > 0) {
8553
                                $relative_path = $relative_path.'/';
8554
                            }
8555
                        } else {
8556
                            $result = $this->generate_lp_folder($_course);
8557
                            $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
8558
                            $relative_prefix = '../../';
8559
                        }
8560
8561
                        $editor_config = [
8562
                            'ToolbarSet' => 'LearningPathDocuments',
8563
                            'Width' => '100%',
8564
                            'Height' => '500',
8565
                            'FullPage' => true,
8566
                            'CreateDocumentDir' => $relative_prefix,
8567
                            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
8568
                            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
8569
                        ];
8570
8571
                        if ($_GET['action'] == 'add_item') {
8572
                            $class = 'add';
8573
                            $text = get_lang('Add this document to the course');
8574
                        } else {
8575
                            if ($_GET['action'] == 'edit_item') {
8576
                                $class = 'save';
8577
                                $text = get_lang('Save document');
8578
                            }
8579
                        }
8580
8581
                        $form->addButtonSave($text, 'submit_button');
8582
                        $renderer = $form->defaultRenderer();
8583
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
8584
                        $form->addElement('html', '<div class="editor-lp">');
8585
                        $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
8586
                        $form->addElement('html', '</div>');
8587
                        $defaults['content_lp'] = $content;
8588
                    }
8589
                } elseif (is_numeric($extra_info)) {
8590
                    $form->addButtonSave(get_lang('Save document'), 'submit_button');
8591
8592
                    $return = $this->display_document($extra_info, true, true, true);
8593
                    $form->addElement('html', $return);
8594
                }
8595
            }
8596
        }
8597
        if (isset($extra_info['item_type']) &&
8598
            $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
8599
        ) {
8600
            $parentSelect->freeze();
8601
            $position->freeze();
8602
        }
8603
8604
        if ($action == 'move') {
8605
            $form->addElement('hidden', 'title', $item_title);
8606
            $form->addElement('hidden', 'description', $item_description);
8607
        }
8608
        if (is_numeric($extra_info)) {
8609
            $form->addButtonSave(get_lang('Save document'), 'submit_button');
8610
            $form->addElement('hidden', 'path', $extra_info);
8611
        } elseif (is_array($extra_info)) {
8612
            $form->addButtonSave(get_lang('Save document'), 'submit_button');
8613
            $form->addElement('hidden', 'path', $extra_info['path']);
8614
        }
8615
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
8616
        $form->addElement('hidden', 'post_time', time());
8617
        $form->setDefaults($defaults);
8618
8619
        return $form->returnForm();
8620
    }
8621
8622
    /**
8623
     * Returns the form to update or create a read-out text.
8624
     *
8625
     * @param string $action     "add" or "edit"
8626
     * @param int    $id         ID of the lp_item (if already exists)
8627
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8628
     *
8629
     * @throws Exception
8630
     * @throws HTML_QuickForm_Error
8631
     *
8632
     * @return string HTML form
8633
     */
8634
    public function displayFrmReadOutText($action = 'add', $id = 0, $extra_info = 'new')
8635
    {
8636
        $course_id = api_get_course_int_id();
8637
        $_course = api_get_course_info();
8638
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8639
8640
        $no_display_edit_textarea = false;
8641
        $item_description = '';
8642
        //If action==edit document
8643
        //We don't display the document form if it's not an editable document (html or txt file)
8644
        if ($action == 'edit') {
8645
            if (is_array($extra_info)) {
8646
                $path_parts = pathinfo($extra_info['dir']);
8647
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
8648
                    $no_display_edit_textarea = true;
8649
                }
8650
            }
8651
        }
8652
        $no_display_add = false;
8653
8654
        $item_title = '';
8655
        $item_description = '';
8656
        if ($id != 0 && is_array($extra_info)) {
8657
            $item_title = stripslashes($extra_info['title']);
8658
            $item_description = stripslashes($extra_info['description']);
8659
            $item_terms = stripslashes($extra_info['terms']);
8660
            if (empty($item_title)) {
8661
                $path_parts = pathinfo($extra_info['path']);
8662
                $item_title = stripslashes($path_parts['filename']);
8663
            }
8664
        } elseif (is_numeric($extra_info)) {
8665
            $sql = "SELECT path, title FROM $tbl_doc WHERE c_id = ".$course_id." AND iid = ".intval($extra_info);
8666
            $result = Database::query($sql);
8667
            $row = Database::fetch_array($result);
8668
            $item_title = $row['title'];
8669
            $item_title = str_replace('_', ' ', $item_title);
8670
            if (empty($item_title)) {
8671
                $path_parts = pathinfo($row['path']);
8672
                $item_title = stripslashes($path_parts['filename']);
8673
            }
8674
        }
8675
8676
        $parent = 0;
8677
        if ($id != 0 && is_array($extra_info)) {
8678
            $parent = $extra_info['parent_item_id'];
8679
        }
8680
8681
        $arrLP = $this->getItemsForForm();
8682
        $this->tree_array($arrLP);
8683
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8684
        unset($this->arrMenu);
8685
8686
        if ($action === 'add') {
8687
            $formHeader = get_lang('Create a new document');
8688
        } else {
8689
            $formHeader = get_lang('Edit the current document');
8690
        }
8691
8692
        if ('edit' === $action) {
8693
            $urlAudioIcon = Display::url(
8694
                Display::return_icon('audio.png', get_lang('Create read-out text'), [], ICON_SIZE_TINY),
8695
                api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'&'
8696
                    .http_build_query(['view' => 'build', 'id' => $id, 'action' => 'add_audio'])
8697
            );
8698
        } else {
8699
            $urlAudioIcon = Display::return_icon('audio.png', get_lang('Create read-out text'), [], ICON_SIZE_TINY);
8700
        }
8701
8702
        $form = new FormValidator(
8703
            'frm_add_reading',
8704
            'POST',
8705
            $this->getCurrentBuildingModeURL(),
8706
            '',
8707
            ['enctype' => 'multipart/form-data']
8708
        );
8709
        $form->addHeader($formHeader);
8710
        $form->addHtml(
8711
            Display::return_message(
8712
                sprintf(get_lang('You need attach a audio file according to the text, clicking on the %s icon.'), $urlAudioIcon),
8713
                'normal',
8714
                false
8715
            )
8716
        );
8717
        $defaults['title'] = !empty($item_title) ? Security::remove_XSS($item_title) : '';
8718
        $defaults['description'] = $item_description;
8719
8720
        $data = $this->generate_lp_folder($_course);
8721
8722
        if ($action != 'edit') {
8723
            $folders = DocumentManager::get_all_document_folders($_course, 0, true);
8724
            DocumentManager::build_directory_selector(
8725
                $folders,
8726
                '',
8727
                [],
8728
                true,
8729
                $form,
8730
                'directory_parent_id'
8731
            );
8732
        }
8733
8734
        if (isset($data['id'])) {
8735
            $defaults['directory_parent_id'] = $data['id'];
8736
        }
8737
        $this->setItemTitle($form);
8738
8739
        $arrHide[0]['value'] = $this->name;
8740
        $arrHide[0]['padding'] = 20;
8741
8742
        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...
8743
            if ($action != 'add') {
8744
                if ($arrLP[$i]['item_type'] == 'dir' &&
8745
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8746
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8747
                ) {
8748
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8749
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8750
                }
8751
            } else {
8752
                if ($arrLP[$i]['item_type'] == 'dir') {
8753
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8754
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8755
                }
8756
            }
8757
        }
8758
8759
        $parent_select = $form->addSelect(
8760
            'parent',
8761
            get_lang('Parent'),
8762
            [],
8763
            ['onchange' => "javascript: load_cbo(this.value, 'frm_add_reading_previous');"]
8764
        );
8765
8766
        $my_count = 0;
8767
        foreach ($arrHide as $key => $value) {
8768
            if ($my_count != 0) {
8769
                // The LP name is also the first section and is not in the same charset like the other sections.
8770
                $value['value'] = Security::remove_XSS($value['value']);
8771
                $parent_select->addOption(
8772
                    $value['value'],
8773
                    $key,
8774
                    'style="padding-left:'.$value['padding'].'px;"'
8775
                );
8776
            } else {
8777
                $value['value'] = Security::remove_XSS($value['value']);
8778
                $parent_select->addOption(
8779
                    $value['value'],
8780
                    $key,
8781
                    'style="padding-left:'.$value['padding'].'px;"'
8782
                );
8783
            }
8784
            $my_count++;
8785
        }
8786
8787
        if (!empty($id)) {
8788
            $parent_select->setSelected($parent);
8789
        } else {
8790
            $parent_item_id = Session::read('parent_item_id', 0);
8791
            $parent_select->setSelected($parent_item_id);
8792
        }
8793
8794
        if (is_array($arrLP)) {
8795
            reset($arrLP);
8796
        }
8797
8798
        $arrHide = [];
8799
        $s_selected_position = null;
8800
8801
        // POSITION
8802
        $lastPosition = null;
8803
8804
        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...
8805
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) &&
8806
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8807
            ) {
8808
                if ((isset($extra_info['previous_item_id']) &&
8809
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
8810
                ) {
8811
                    $s_selected_position = $arrLP[$i]['id'];
8812
                }
8813
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8814
            }
8815
            $lastPosition = $arrLP[$i]['id'];
8816
        }
8817
8818
        if (empty($s_selected_position)) {
8819
            $s_selected_position = $lastPosition;
8820
        }
8821
8822
        $position = $form->addSelect(
8823
            'previous',
8824
            get_lang('Position'),
8825
            []
8826
        );
8827
        $position->addOption(get_lang('First position'), 0);
8828
8829
        foreach ($arrHide as $key => $value) {
8830
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8831
            $position->addOption(
8832
                $value['value'],
8833
                $key,
8834
                'style="padding-left:'.$padding.'px;"'
8835
            );
8836
        }
8837
        $position->setSelected($s_selected_position);
8838
8839
        if (is_array($arrLP)) {
8840
            reset($arrLP);
8841
        }
8842
8843
        $arrHide = [];
8844
8845
        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...
8846
            if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
8847
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8848
            ) {
8849
                $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8850
            }
8851
        }
8852
8853
        if (!$no_display_add) {
8854
            $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8855
            $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8856
8857
            if ($extra_info == 'new' || $item_type == TOOL_READOUT_TEXT || $edit == 'true') {
8858
                if (!$no_display_edit_textarea) {
8859
                    $content = '';
8860
8861
                    if (isset($_POST['content'])) {
8862
                        $content = stripslashes($_POST['content']);
8863
                    } elseif (is_array($extra_info)) {
8864
                        $content = $this->display_document($extra_info['path'], false, false);
8865
                    } elseif (is_numeric($extra_info)) {
8866
                        $content = $this->display_document($extra_info, false, false);
8867
                    }
8868
8869
                    // A new document, it is in the root of the repository.
8870
                    if (is_array($extra_info) && $extra_info != 'new') {
8871
                    } else {
8872
                        $this->generate_lp_folder($_course);
8873
                    }
8874
8875
                    if ($_GET['action'] == 'add_item') {
8876
                        $text = get_lang('Add this document to the course');
8877
                    } else {
8878
                        $text = get_lang('Save document');
8879
                    }
8880
8881
                    $form->addTextarea('content_lp', get_lang('Content'), ['rows' => 20]);
8882
                    $form
8883
                        ->defaultRenderer()
8884
                        ->setElementTemplate($form->getDefaultElementTemplate(), 'content_lp');
8885
                    $form->addButtonSave($text, 'submit_button');
8886
                    $defaults['content_lp'] = $content;
8887
                }
8888
            } elseif (is_numeric($extra_info)) {
8889
                $form->addButtonSave(get_lang('Save document'), 'submit_button');
8890
8891
                $return = $this->display_document($extra_info, true, true, true);
8892
                $form->addElement('html', $return);
8893
            }
8894
        }
8895
8896
        if (is_numeric($extra_info)) {
8897
            $form->addElement('hidden', 'path', $extra_info);
8898
        } elseif (is_array($extra_info)) {
8899
            $form->addElement('hidden', 'path', $extra_info['path']);
8900
        }
8901
8902
        $form->addElement('hidden', 'type', TOOL_READOUT_TEXT);
8903
        $form->addElement('hidden', 'post_time', time());
8904
        $form->setDefaults($defaults);
8905
8906
        return $form->returnForm();
8907
    }
8908
8909
    /**
8910
     * @param array  $courseInfo
8911
     * @param string $content
8912
     * @param string $title
8913
     * @param int    $parentId
8914
     *
8915
     * @throws \Doctrine\ORM\ORMException
8916
     * @throws \Doctrine\ORM\OptimisticLockException
8917
     * @throws \Doctrine\ORM\TransactionRequiredException
8918
     *
8919
     * @return int
8920
     */
8921
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
8922
    {
8923
        $creatorId = api_get_user_id();
8924
        $sessionId = api_get_session_id();
8925
8926
        // Generates folder
8927
        $result = $this->generate_lp_folder($courseInfo);
8928
        $dir = $result['dir'];
8929
8930
        if (empty($parentId) || $parentId == '/') {
8931
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
8932
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
8933
8934
            if ($parentId === '/') {
8935
                $dir = '/';
8936
            }
8937
8938
            // Please, do not modify this dirname formatting.
8939
            if (strstr($dir, '..')) {
8940
                $dir = '/';
8941
            }
8942
8943
            if (!empty($dir[0]) && $dir[0] == '.') {
8944
                $dir = substr($dir, 1);
8945
            }
8946
            if (!empty($dir[0]) && $dir[0] != '/') {
8947
                $dir = '/'.$dir;
8948
            }
8949
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
8950
                $dir .= '/';
8951
            }
8952
        } else {
8953
            $parentInfo = DocumentManager::get_document_data_by_id(
8954
                $parentId,
8955
                $courseInfo['code']
8956
            );
8957
            if (!empty($parentInfo)) {
8958
                $dir = $parentInfo['path'].'/';
8959
            }
8960
        }
8961
8962
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
8963
8964
        if (!is_dir($filepath)) {
8965
            $dir = '/';
8966
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
8967
        }
8968
8969
        $originalTitle = !empty($title) ? $title : $_POST['title'];
8970
8971
        if (!empty($title)) {
8972
            $title = api_replace_dangerous_char(stripslashes($title));
8973
        } else {
8974
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
8975
        }
8976
8977
        $title = disable_dangerous_file($title);
8978
        $filename = $title;
8979
        $content = !empty($content) ? $content : $_POST['content_lp'];
8980
        $tmpFileName = $filename;
8981
8982
        $i = 0;
8983
        while (file_exists($filepath.$tmpFileName.'.html')) {
8984
            $tmpFileName = $filename.'_'.++$i;
8985
        }
8986
8987
        $filename = $tmpFileName.'.html';
8988
        $content = stripslashes($content);
8989
8990
        if (file_exists($filepath.$filename)) {
8991
            return 0;
8992
        }
8993
8994
        $putContent = file_put_contents($filepath.$filename, $content);
8995
8996
        if ($putContent === false) {
8997
            return 0;
8998
        }
8999
9000
        $fileSize = filesize($filepath.$filename);
9001
        $saveFilePath = $dir.$filename;
9002
9003
        $document = DocumentManager::addDocument(
9004
            $courseInfo,
9005
            $saveFilePath,
9006
            'file',
9007
            $fileSize,
9008
            $tmpFileName,
9009
            '',
9010
            0, //readonly
9011
            true,
9012
            null,
9013
            $sessionId,
9014
            $creatorId
9015
        );
9016
9017
        $documentId = $document->getId();
9018
9019
        if (!$document) {
9020
            return 0;
9021
        }
9022
9023
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
9024
        $newTitle = $originalTitle;
9025
9026
        if ($newComment || $newTitle) {
9027
            $em = Database::getManager();
9028
9029
            if ($newComment) {
9030
                $document->setComment($newComment);
9031
            }
9032
9033
            if ($newTitle) {
9034
                $document->setTitle($newTitle);
9035
            }
9036
9037
            $em->persist($document);
9038
            $em->flush();
9039
        }
9040
9041
        return $documentId;
9042
    }
9043
9044
    /**
9045
     * Return HTML form to add/edit a link item.
9046
     *
9047
     * @param string $action     (add/edit)
9048
     * @param int    $id         Item ID if exists
9049
     * @param mixed  $extra_info
9050
     *
9051
     * @throws Exception
9052
     * @throws HTML_QuickForm_Error
9053
     *
9054
     * @return string HTML form
9055
     */
9056
    public function display_link_form($action = 'add', $id = 0, $extra_info = '')
9057
    {
9058
        $course_id = api_get_course_int_id();
9059
        $tbl_link = Database::get_course_table(TABLE_LINK);
9060
9061
        $item_title = '';
9062
        $item_description = '';
9063
        $item_url = '';
9064
9065
        if ($id != 0 && is_array($extra_info)) {
9066
            $item_title = stripslashes($extra_info['title']);
9067
            $item_description = stripslashes($extra_info['description']);
9068
            $item_url = stripslashes($extra_info['url']);
9069
        } elseif (is_numeric($extra_info)) {
9070
            $extra_info = (int) $extra_info;
9071
            $sql = "SELECT title, description, url
9072
                    FROM $tbl_link
9073
                    WHERE c_id = $course_id AND iid = $extra_info";
9074
            $result = Database::query($sql);
9075
            $row = Database::fetch_array($result);
9076
            $item_title = $row['title'];
9077
            $item_description = $row['description'];
9078
            $item_url = $row['url'];
9079
        }
9080
9081
        $form = new FormValidator(
9082
            'edit_link',
9083
            'POST',
9084
            $this->getCurrentBuildingModeURL()
9085
        );
9086
        $defaults = [];
9087
        $parent = 0;
9088
        if ($id != 0 && is_array($extra_info)) {
9089
            $parent = $extra_info['parent_item_id'];
9090
        }
9091
9092
        $arrLP = $this->getItemsForForm();
9093
9094
        $this->tree_array($arrLP);
9095
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9096
        unset($this->arrMenu);
9097
9098
        if ($action == 'add') {
9099
            $legend = get_lang('Adding a link to the course');
9100
        } elseif ($action == 'move') {
9101
            $legend = get_lang('Move the current link');
9102
        } else {
9103
            $legend = get_lang('Edit the current link');
9104
        }
9105
9106
        $form->addHeader($legend);
9107
9108
        if ($action != 'move') {
9109
            $this->setItemTitle($form);
9110
            $defaults['title'] = $item_title;
9111
        }
9112
9113
        $selectParent = $form->addSelect(
9114
            'parent',
9115
            get_lang('Parent'),
9116
            [],
9117
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
9118
        );
9119
        $selectParent->addOption($this->name, 0);
9120
        $arrHide = [
9121
            $id,
9122
        ];
9123
9124
        $parent_item_id = Session::read('parent_item_id', 0);
9125
9126
        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...
9127
            if ($action != 'add') {
9128
                if (
9129
                    ($arrLP[$i]['item_type'] == 'dir') &&
9130
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9131
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9132
                ) {
9133
                    $selectParent->addOption(
9134
                        $arrLP[$i]['title'],
9135
                        $arrLP[$i]['id'],
9136
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
9137
                    );
9138
9139
                    if ($parent == $arrLP[$i]['id']) {
9140
                        $selectParent->setSelected($arrLP[$i]['id']);
9141
                    }
9142
                } else {
9143
                    $arrHide[] = $arrLP[$i]['id'];
9144
                }
9145
            } else {
9146
                if ($arrLP[$i]['item_type'] == 'dir') {
9147
                    $selectParent->addOption(
9148
                        $arrLP[$i]['title'],
9149
                        $arrLP[$i]['id'],
9150
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
9151
                    );
9152
9153
                    if ($parent_item_id == $arrLP[$i]['id']) {
9154
                        $selectParent->setSelected($arrLP[$i]['id']);
9155
                    }
9156
                }
9157
            }
9158
        }
9159
9160
        if (is_array($arrLP)) {
9161
            reset($arrLP);
9162
        }
9163
9164
        $selectPrevious = $form->addSelect(
9165
            'previous',
9166
            get_lang('Position'),
9167
            [],
9168
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9169
        );
9170
        $selectPrevious->addOption(get_lang('First position'), 0);
9171
9172
        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...
9173
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9174
                $selectPrevious->addOption(
9175
                    $arrLP[$i]['title'],
9176
                    $arrLP[$i]['id']
9177
                );
9178
9179
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9180
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9181
                } elseif ($action == 'add') {
9182
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9183
                }
9184
            }
9185
        }
9186
9187
        if ($action != 'move') {
9188
            $urlAttributes = ['class' => 'learnpath_item_form'];
9189
9190
            if (is_numeric($extra_info)) {
9191
                $urlAttributes['disabled'] = 'disabled';
9192
            }
9193
9194
            $form->addElement('url', 'url', get_lang('URL'), $urlAttributes);
9195
            $defaults['url'] = $item_url;
9196
            $arrHide = [];
9197
            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...
9198
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
9199
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9200
                }
9201
            }
9202
        }
9203
9204
        if ($action == 'add') {
9205
            $form->addButtonSave(get_lang('Add link to course'), 'submit_button');
9206
        } else {
9207
            $form->addButtonSave(get_lang('Edit the current link'), 'submit_button');
9208
        }
9209
9210
        if ($action == 'move') {
9211
            $form->addHidden('title', $item_title);
9212
            $form->addHidden('description', $item_description);
9213
        }
9214
9215
        if (is_numeric($extra_info)) {
9216
            $form->addHidden('path', $extra_info);
9217
        } elseif (is_array($extra_info)) {
9218
            $form->addHidden('path', $extra_info['path']);
9219
        }
9220
        $form->addHidden('type', TOOL_LINK);
9221
        $form->addHidden('post_time', time());
9222
        $form->setDefaults($defaults);
9223
9224
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
9225
    }
9226
9227
    /**
9228
     * Return HTML form to add/edit a student publication (work).
9229
     *
9230
     * @param string $action
9231
     * @param int    $id         Item ID if already exists
9232
     * @param string $extra_info
9233
     *
9234
     * @throws Exception
9235
     *
9236
     * @return string HTML form
9237
     */
9238
    public function display_student_publication_form(
9239
        $action = 'add',
9240
        $id = 0,
9241
        $extra_info = ''
9242
    ) {
9243
        $course_id = api_get_course_int_id();
9244
        $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
9245
9246
        $item_title = get_lang('Assignments');
9247
        if ($id != 0 && is_array($extra_info)) {
9248
            $item_title = stripslashes($extra_info['title']);
9249
            $item_description = stripslashes($extra_info['description']);
9250
        } elseif (is_numeric($extra_info)) {
9251
            $extra_info = (int) $extra_info;
9252
            $sql = "SELECT title, description
9253
                    FROM $tbl_publication
9254
                    WHERE c_id = $course_id AND id = ".$extra_info;
9255
9256
            $result = Database::query($sql);
9257
            $row = Database::fetch_array($result);
9258
            if ($row) {
9259
                $item_title = $row['title'];
9260
            }
9261
        }
9262
9263
        $parent = 0;
9264
        if ($id != 0 && is_array($extra_info)) {
9265
            $parent = $extra_info['parent_item_id'];
9266
        }
9267
9268
        $arrLP = $this->getItemsForForm();
9269
9270
        $this->tree_array($arrLP);
9271
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9272
        unset($this->arrMenu);
9273
9274
        $form = new FormValidator('frm_student_publication', 'post', '#');
9275
9276
        if ($action == 'add') {
9277
            $form->addHeader(get_lang('Assignments'));
9278
        } elseif ($action == 'move') {
9279
            $form->addHeader(get_lang('Move the current assignment'));
9280
        } else {
9281
            $form->addHeader(get_lang('Edit the current assignment'));
9282
        }
9283
9284
        if ($action != 'move') {
9285
            $this->setItemTitle($form);
9286
        }
9287
9288
        $parentSelect = $form->addSelect(
9289
            'parent',
9290
            get_lang('Parent'),
9291
            ['0' => $this->name],
9292
            [
9293
                'onchange' => 'javascript: load_cbo(this.value);',
9294
                'class' => 'learnpath_item_form',
9295
                'id' => 'idParent',
9296
            ]
9297
        );
9298
9299
        $arrHide = [$id];
9300
        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...
9301
            if ($action != 'add') {
9302
                if (
9303
                    ($arrLP[$i]['item_type'] == 'dir') &&
9304
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9305
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9306
                ) {
9307
                    $parentSelect->addOption(
9308
                        $arrLP[$i]['title'],
9309
                        $arrLP[$i]['id'],
9310
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9311
                    );
9312
9313
                    if ($parent == $arrLP[$i]['id']) {
9314
                        $parentSelect->setSelected($arrLP[$i]['id']);
9315
                    }
9316
                } else {
9317
                    $arrHide[] = $arrLP[$i]['id'];
9318
                }
9319
            } else {
9320
                if ($arrLP[$i]['item_type'] == 'dir') {
9321
                    $parentSelect->addOption(
9322
                        $arrLP[$i]['title'],
9323
                        $arrLP[$i]['id'],
9324
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9325
                    );
9326
9327
                    if ($parent == $arrLP[$i]['id']) {
9328
                        $parentSelect->setSelected($arrLP[$i]['id']);
9329
                    }
9330
                }
9331
            }
9332
        }
9333
9334
        if (is_array($arrLP)) {
9335
            reset($arrLP);
9336
        }
9337
9338
        $previousSelect = $form->addSelect(
9339
            'previous',
9340
            get_lang('Position'),
9341
            ['0' => get_lang('First position')],
9342
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9343
        );
9344
9345
        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...
9346
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9347
                $previousSelect->addOption(
9348
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
9349
                    $arrLP[$i]['id']
9350
                );
9351
9352
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9353
                    $previousSelect->setSelected($arrLP[$i]['id']);
9354
                } elseif ($action == 'add') {
9355
                    $previousSelect->setSelected($arrLP[$i]['id']);
9356
                }
9357
            }
9358
        }
9359
9360
        if ($action == 'add') {
9361
            $form->addButtonCreate(get_lang('Add assignment to course'), 'submit_button');
9362
        } else {
9363
            $form->addButtonCreate(get_lang('Edit the current assignment'), 'submit_button');
9364
        }
9365
9366
        if ($action == 'move') {
9367
            $form->addHidden('title', $item_title);
9368
            $form->addHidden('description', $item_description);
9369
        }
9370
9371
        if (is_numeric($extra_info)) {
9372
            $form->addHidden('path', $extra_info);
9373
        } elseif (is_array($extra_info)) {
9374
            $form->addHidden('path', $extra_info['path']);
9375
        }
9376
9377
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
9378
        $form->addHidden('post_time', time());
9379
        $form->setDefaults(['title' => $item_title]);
9380
9381
        $return = '<div class="sectioncomment">';
9382
        $return .= $form->returnForm();
9383
        $return .= '</div>';
9384
9385
        return $return;
9386
    }
9387
9388
    /**
9389
     * Displays the menu for manipulating a step.
9390
     *
9391
     * @param id     $item_id
9392
     * @param string $item_type
9393
     *
9394
     * @return string
9395
     */
9396
    public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
9397
    {
9398
        $_course = api_get_course_info();
9399
        $course_code = api_get_course_id();
9400
        $item_id = (int) $item_id;
9401
9402
        $return = '<div class="actions">';
9403
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9404
        $sql = "SELECT * FROM $tbl_lp_item
9405
                WHERE iid = ".$item_id;
9406
        $result = Database::query($sql);
9407
        $row = Database::fetch_assoc($result);
9408
9409
        $audio_player = null;
9410
        // We display an audio player if needed.
9411
        if (!empty($row['audio'])) {
9412
            $webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
9413
9414
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
9415
                .'<audio src="'.$webAudioPath.'" controls>'
9416
                .'</div><br>';
9417
        }
9418
9419
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
9420
9421
        if ($item_type != TOOL_LP_FINAL_ITEM) {
9422
            $return .= Display::url(
9423
                Display::return_icon(
9424
                    'edit.png',
9425
                    get_lang('Edit'),
9426
                    [],
9427
                    ICON_SIZE_SMALL
9428
                ),
9429
                $url.'&action=edit_item&path_item='.$row['path']
9430
            );
9431
9432
            $return .= Display::url(
9433
                Display::return_icon(
9434
                    'move.png',
9435
                    get_lang('Move'),
9436
                    [],
9437
                    ICON_SIZE_SMALL
9438
                ),
9439
                $url.'&action=move_item'
9440
            );
9441
        }
9442
9443
        // Commented for now as prerequisites cannot be added to chapters.
9444
        if ($item_type != 'dir') {
9445
            $return .= Display::url(
9446
                Display::return_icon(
9447
                    'accept.png',
9448
                    get_lang('Prerequisites'),
9449
                    [],
9450
                    ICON_SIZE_SMALL
9451
                ),
9452
                $url.'&action=edit_item_prereq'
9453
            );
9454
        }
9455
        $return .= Display::url(
9456
            Display::return_icon(
9457
                'delete.png',
9458
                get_lang('Delete'),
9459
                [],
9460
                ICON_SIZE_SMALL
9461
            ),
9462
            $url.'&action=delete_item'
9463
        );
9464
9465
        if (in_array($item_type, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
9466
            $documentData = DocumentManager::get_document_data_by_id($row['path'], $course_code);
9467
            if (empty($documentData)) {
9468
                // Try with iid
9469
                $table = Database::get_course_table(TABLE_DOCUMENT);
9470
                $sql = "SELECT path FROM $table
9471
                        WHERE
9472
                              c_id = ".api_get_course_int_id()." AND
9473
                              iid = ".$row['path']." AND
9474
                              path NOT LIKE '%_DELETED_%'";
9475
                $result = Database::query($sql);
9476
                $documentData = Database::fetch_array($result);
9477
                if ($documentData) {
9478
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
9479
                }
9480
            }
9481
            if (isset($documentData['absolute_path_from_document'])) {
9482
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
9483
            }
9484
        }
9485
9486
        $return .= '</div>';
9487
9488
        if (!empty($audio_player)) {
9489
            $return .= $audio_player;
9490
        }
9491
9492
        return $return;
9493
    }
9494
9495
    /**
9496
     * Creates the javascript needed for filling up the checkboxes without page reload.
9497
     *
9498
     * @return string
9499
     */
9500
    public function get_js_dropdown_array()
9501
    {
9502
        $course_id = api_get_course_int_id();
9503
        $return = 'var child_name = new Array();'."\n";
9504
        $return .= 'var child_value = new Array();'."\n\n";
9505
        $return .= 'child_name[0] = new Array();'."\n";
9506
        $return .= 'child_value[0] = new Array();'."\n\n";
9507
9508
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9509
        $sql = "SELECT * FROM ".$tbl_lp_item."
9510
                WHERE
9511
                    c_id = $course_id AND
9512
                    lp_id = ".$this->lp_id." AND
9513
                    parent_item_id = 0
9514
                ORDER BY display_order ASC";
9515
        $res_zero = Database::query($sql);
9516
        $i = 0;
9517
9518
        $list = $this->getItemsForForm(true);
9519
9520
        foreach ($list as $row_zero) {
9521
            if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
9522
                if ($row_zero['item_type'] == TOOL_QUIZ) {
9523
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
9524
                }
9525
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
9526
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
9527
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
9528
            }
9529
        }
9530
9531
        $return .= "\n";
9532
        $sql = "SELECT * FROM $tbl_lp_item
9533
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9534
        $res = Database::query($sql);
9535
        while ($row = Database::fetch_array($res)) {
9536
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
9537
                           WHERE
9538
                                c_id = ".$course_id." AND
9539
                                parent_item_id = ".$row['iid']."
9540
                           ORDER BY display_order ASC";
9541
            $res_parent = Database::query($sql_parent);
9542
            $i = 0;
9543
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
9544
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
9545
9546
            while ($row_parent = Database::fetch_array($res_parent)) {
9547
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
9548
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
9549
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
9550
            }
9551
            $return .= "\n";
9552
        }
9553
9554
        $return .= "
9555
            function load_cbo(id) {
9556
                if (!id) {
9557
                    return false;
9558
                }
9559
9560
                var cbo = document.getElementById('previous');
9561
                for(var i = cbo.length - 1; i > 0; i--) {
9562
                    cbo.options[i] = null;
9563
                }
9564
9565
                var k=0;
9566
                for(var i = 1; i <= child_name[id].length; i++){
9567
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
9568
                    option.style.paddingLeft = '40px';
9569
                    cbo.options[i] = option;
9570
                    k = i;
9571
                }
9572
9573
                cbo.options[k].selected = true;
9574
                $('#previous').selectpicker('refresh');
9575
            }";
9576
9577
        return $return;
9578
    }
9579
9580
    /**
9581
     * Display the form to allow moving an item.
9582
     *
9583
     * @param int $item_id Item ID
9584
     *
9585
     * @throws Exception
9586
     * @throws HTML_QuickForm_Error
9587
     *
9588
     * @return string HTML form
9589
     */
9590
    public function display_move_item($item_id)
9591
    {
9592
        $return = '';
9593
        if (is_numeric($item_id)) {
9594
            $item_id = (int) $item_id;
9595
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9596
9597
            $sql = "SELECT * FROM $tbl_lp_item
9598
                    WHERE iid = $item_id";
9599
            $res = Database::query($sql);
9600
            $row = Database::fetch_array($res);
9601
9602
            switch ($row['item_type']) {
9603
                case 'dir':
9604
                case 'asset':
9605
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9606
                    $return .= $this->display_item_form(
9607
                        $row['item_type'],
9608
                        get_lang('Move the current section'),
9609
                        'move',
9610
                        $item_id,
9611
                        $row
9612
                    );
9613
                    break;
9614
                case TOOL_DOCUMENT:
9615
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9616
                    $return .= $this->display_document_form('move', $item_id, $row);
9617
                    break;
9618
                case TOOL_LINK:
9619
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9620
                    $return .= $this->display_link_form('move', $item_id, $row);
9621
                    break;
9622
                case TOOL_HOTPOTATOES:
9623
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9624
                    $return .= $this->display_link_form('move', $item_id, $row);
9625
                    break;
9626
                case TOOL_QUIZ:
9627
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9628
                    $return .= $this->display_quiz_form('move', $item_id, $row);
9629
                    break;
9630
                case TOOL_STUDENTPUBLICATION:
9631
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9632
                    $return .= $this->display_student_publication_form('move', $item_id, $row);
9633
                    break;
9634
                case TOOL_FORUM:
9635
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9636
                    $return .= $this->display_forum_form('move', $item_id, $row);
9637
                    break;
9638
                case TOOL_THREAD:
9639
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9640
                    $return .= $this->display_forum_form('move', $item_id, $row);
9641
                    break;
9642
            }
9643
        }
9644
9645
        return $return;
9646
    }
9647
9648
    /**
9649
     * Return HTML form to allow prerequisites selection.
9650
     *
9651
     * @todo use FormValidator
9652
     *
9653
     * @param int Item ID
9654
     *
9655
     * @return string HTML form
9656
     */
9657
    public function display_item_prerequisites_form($item_id = 0)
9658
    {
9659
        $course_id = api_get_course_int_id();
9660
        $item_id = (int) $item_id;
9661
9662
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9663
9664
        /* Current prerequisite */
9665
        $sql = "SELECT * FROM $tbl_lp_item
9666
                WHERE iid = $item_id";
9667
        $result = Database::query($sql);
9668
        $row = Database::fetch_array($result);
9669
        $prerequisiteId = $row['prerequisite'];
9670
        $return = '<legend>';
9671
        $return .= get_lang('Add/edit prerequisites');
9672
        $return .= '</legend>';
9673
        $return .= '<form method="POST">';
9674
        $return .= '<div class="table-responsive">';
9675
        $return .= '<table class="table table-hover">';
9676
        $return .= '<thead>';
9677
        $return .= '<tr>';
9678
        $return .= '<th>'.get_lang('Prerequisites').'</th>';
9679
        $return .= '<th width="140">'.get_lang('minimum').'</th>';
9680
        $return .= '<th width="140">'.get_lang('maximum').'</th>';
9681
        $return .= '</tr>';
9682
        $return .= '</thead>';
9683
9684
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
9685
        $return .= '<tbody>';
9686
        $return .= '<tr>';
9687
        $return .= '<td colspan="3">';
9688
        $return .= '<div class="radio learnpath"><label for="idnone">';
9689
        $return .= '<input checked="checked" id="idnone" name="prerequisites" type="radio" />';
9690
        $return .= get_lang('none').'</label>';
9691
        $return .= '</div>';
9692
        $return .= '</tr>';
9693
9694
        $sql = "SELECT * FROM $tbl_lp_item
9695
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9696
        $result = Database::query($sql);
9697
9698
        $selectedMinScore = [];
9699
        $selectedMaxScore = [];
9700
        $masteryScore = [];
9701
        while ($row = Database::fetch_array($result)) {
9702
            if ($row['iid'] == $item_id) {
9703
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
9704
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
9705
            }
9706
            $masteryScore[$row['iid']] = $row['mastery_score'];
9707
        }
9708
9709
        $arrLP = $this->getItemsForForm();
9710
        $this->tree_array($arrLP);
9711
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9712
        unset($this->arrMenu);
9713
9714
        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...
9715
            $item = $arrLP[$i];
9716
9717
            if ($item['id'] == $item_id) {
9718
                break;
9719
            }
9720
9721
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
9722
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
9723
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
9724
9725
            $return .= '<tr>';
9726
            $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
9727
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
9728
            $return .= '<label for="id'.$item['id'].'">';
9729
            $return .= '<input'.(in_array($prerequisiteId, [$item['id'], $item['ref']]) ? ' checked="checked" ' : '').($item['item_type'] == 'dir' ? ' disabled="disabled" ' : ' ').'id="id'.$item['id'].'" name="prerequisites"  type="radio" value="'.$item['id'].'" />';
9730
9731
            $icon_name = str_replace(' ', '', $item['item_type']);
9732
9733
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
9734
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
9735
            } else {
9736
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
9737
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
9738
                } else {
9739
                    $return .= Display::return_icon('folder_document.png');
9740
                }
9741
            }
9742
9743
            $return .= $item['title'].'</label>';
9744
            $return .= '</div>';
9745
            $return .= '</td>';
9746
9747
            if ($item['item_type'] == TOOL_QUIZ) {
9748
                // lets update max_score Tests information depending of the Tests Advanced properties
9749
                $lpItemObj = new LpItem($course_id, $item['id']);
9750
                $exercise = new Exercise($course_id);
9751
                $exercise->read($lpItemObj->path);
9752
                $lpItemObj->max_score = $exercise->get_max_score();
9753
                $lpItemObj->update();
9754
                $item['max_score'] = $lpItemObj->max_score;
9755
9756
                if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
9757
                    // Backwards compatibility with 1.9.x use mastery_score as min value
9758
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
9759
                }
9760
9761
                $return .= '<td>';
9762
                $return .= '<input
9763
                    class="form-control"
9764
                    size="4" maxlength="3"
9765
                    name="min_'.$item['id'].'"
9766
                    type="number"
9767
                    min="0"
9768
                    step="1"
9769
                    max="'.$item['max_score'].'"
9770
                    value="'.$selectedMinScoreValue.'"
9771
                />';
9772
                $return .= '</td>';
9773
                $return .= '<td>';
9774
                $return .= '<input
9775
                    class="form-control"
9776
                    size="4"
9777
                    maxlength="3"
9778
                    name="max_'.$item['id'].'"
9779
                    type="number"
9780
                    min="0"
9781
                    step="1"
9782
                    max="'.$item['max_score'].'"
9783
                    value="'.$selectedMaxScoreValue.'"
9784
                />';
9785
                $return .= '</td>';
9786
            }
9787
9788
            if ($item['item_type'] == TOOL_HOTPOTATOES) {
9789
                $return .= '<td>';
9790
                $return .= '<input
9791
                    size="4"
9792
                    maxlength="3"
9793
                    name="min_'.$item['id'].'"
9794
                    type="number"
9795
                    min="0"
9796
                    step="1"
9797
                    max="'.$item['max_score'].'"
9798
                    value="'.$selectedMinScoreValue.'"
9799
                />';
9800
                $return .= '</td>';
9801
                $return .= '<td>';
9802
                $return .= '<input
9803
                    size="4"
9804
                    maxlength="3"
9805
                    name="max_'.$item['id'].'"
9806
                    type="number"
9807
                    min="0"
9808
                    step="1"
9809
                    max="'.$item['max_score'].'"
9810
                    value="'.$selectedMaxScoreValue.'"
9811
                />';
9812
                $return .= '</td>';
9813
            }
9814
            $return .= '</tr>';
9815
        }
9816
        $return .= '<tr>';
9817
        $return .= '</tr>';
9818
        $return .= '</tbody>';
9819
        $return .= '</table>';
9820
        $return .= '</div>';
9821
        $return .= '<div class="form-group">';
9822
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
9823
            get_lang('Save prerequisites settings').'</button>';
9824
        $return .= '</form>';
9825
9826
        return $return;
9827
    }
9828
9829
    /**
9830
     * Return HTML list to allow prerequisites selection for lp.
9831
     *
9832
     * @return string HTML form
9833
     */
9834
    public function display_lp_prerequisites_list()
9835
    {
9836
        $course_id = api_get_course_int_id();
9837
        $lp_id = $this->lp_id;
9838
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
9839
9840
        // get current prerequisite
9841
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
9842
        $result = Database::query($sql);
9843
        $row = Database::fetch_array($result);
9844
        $prerequisiteId = $row['prerequisite'];
9845
        $session_id = api_get_session_id();
9846
        $session_condition = api_get_session_condition($session_id, true, true);
9847
        $sql = "SELECT * FROM $tbl_lp
9848
                WHERE c_id = $course_id $session_condition
9849
                ORDER BY display_order ";
9850
        $rs = Database::query($sql);
9851
        $return = '';
9852
        $return .= '<select name="prerequisites" class="form-control">';
9853
        $return .= '<option value="0">'.get_lang('none').'</option>';
9854
        if (Database::num_rows($rs) > 0) {
9855
            while ($row = Database::fetch_array($rs)) {
9856
                if ($row['id'] == $lp_id) {
9857
                    continue;
9858
                }
9859
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
9860
            }
9861
        }
9862
        $return .= '</select>';
9863
9864
        return $return;
9865
    }
9866
9867
    /**
9868
     * Creates a list with all the documents in it.
9869
     *
9870
     * @param bool $showInvisibleFiles
9871
     *
9872
     * @throws Exception
9873
     * @throws HTML_QuickForm_Error
9874
     *
9875
     * @return string
9876
     */
9877
    public function get_documents($showInvisibleFiles = false)
9878
    {
9879
        $course_info = api_get_course_info();
9880
        $sessionId = api_get_session_id();
9881
        $documentTree = DocumentManager::get_document_preview(
9882
            $course_info,
9883
            $this->lp_id,
9884
            null,
9885
            $sessionId,
9886
            true,
9887
            null,
9888
            null,
9889
            $showInvisibleFiles,
9890
            true
9891
        );
9892
9893
        $headers = [
9894
            get_lang('Files'),
9895
            get_lang('Create a new document'),
9896
            get_lang('Create read-out text'),
9897
            get_lang('Upload'),
9898
        ];
9899
9900
        $form = new FormValidator(
9901
            'form_upload',
9902
            'POST',
9903
            $this->getCurrentBuildingModeURL(),
9904
            '',
9905
            ['enctype' => 'multipart/form-data']
9906
        );
9907
9908
        $folders = DocumentManager::get_all_document_folders(
9909
            api_get_course_info(),
9910
            0,
9911
            true
9912
        );
9913
9914
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
9915
9916
        DocumentManager::build_directory_selector(
9917
            $folders,
9918
            $lpPathInfo['id'],
9919
            [],
9920
            true,
9921
            $form,
9922
            'directory_parent_id'
9923
        );
9924
9925
        $group = [
9926
            $form->createElement(
9927
                'radio',
9928
                'if_exists',
9929
                get_lang('If file exists:'),
9930
                get_lang('Do nothing'),
9931
                'nothing'
9932
            ),
9933
            $form->createElement(
9934
                'radio',
9935
                'if_exists',
9936
                null,
9937
                get_lang('Overwrite the existing file'),
9938
                'overwrite'
9939
            ),
9940
            $form->createElement(
9941
                'radio',
9942
                'if_exists',
9943
                null,
9944
                get_lang('Rename the uploaded file if it exists'),
9945
                'rename'
9946
            ),
9947
        ];
9948
        $form->addGroup($group, null, get_lang('If file exists:'));
9949
9950
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
9951
        $defaultFileExistsOption = 'rename';
9952
        if (!empty($fileExistsOption)) {
9953
            $defaultFileExistsOption = $fileExistsOption;
9954
        }
9955
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
9956
9957
        // Check box options
9958
        $form->addElement(
9959
            'checkbox',
9960
            'unzip',
9961
            get_lang('Options'),
9962
            get_lang('Uncompress zip')
9963
        );
9964
9965
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
9966
        $form->addMultipleUpload($url);
9967
        $new = $this->display_document_form('add', 0);
9968
        $frmReadOutText = $this->displayFrmReadOutText('add');
9969
        $tabs = Display::tabs(
9970
            $headers,
9971
            [$documentTree, $new, $frmReadOutText, $form->returnForm()],
9972
            'subtab'
9973
        );
9974
9975
        return $tabs;
9976
    }
9977
9978
    /**
9979
     * Creates a list with all the exercises (quiz) in it.
9980
     *
9981
     * @return string
9982
     */
9983
    public function get_exercises()
9984
    {
9985
        $course_id = api_get_course_int_id();
9986
        $session_id = api_get_session_id();
9987
        $userInfo = api_get_user_info();
9988
9989
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
9990
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
9991
        $condition_session = api_get_session_condition($session_id, true, true);
9992
        $setting = api_get_setting('lp.show_invisible_exercise_in_lp_toc') === 'true';
9993
9994
        $activeCondition = ' active <> -1 ';
9995
        if ($setting) {
9996
            $activeCondition = ' active = 1 ';
9997
        }
9998
9999
        $categoryCondition = '';
10000
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
10001
        if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
10002
            $categoryCondition = " AND exercise_category_id = $categoryId ";
10003
        }
10004
10005
        $keywordCondition = '';
10006
        $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
10007
10008
        if (!empty($keyword)) {
10009
            $keyword = Database::escape_string($keyword);
10010
            $keywordCondition = " AND title LIKE '%$keyword%' ";
10011
        }
10012
10013
        $sql_quiz = "SELECT * FROM $tbl_quiz
10014
                     WHERE
10015
                            c_id = $course_id AND
10016
                            $activeCondition
10017
                            $condition_session
10018
                            $categoryCondition
10019
                            $keywordCondition
10020
                     ORDER BY title ASC";
10021
        $res_quiz = Database::query($sql_quiz);
10022
10023
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
10024
10025
        // Create a search-box
10026
        $form = new FormValidator('search_simple', 'get', $currentUrl);
10027
        $form->addHidden('action', 'add_item');
10028
        $form->addHidden('type', 'step');
10029
        $form->addHidden('lp_id', $this->lp_id);
10030
        $form->addHidden('lp_build_selected', '2');
10031
10032
        $form->addCourseHiddenParams();
10033
        $form->addText(
10034
            'keyword',
10035
            get_lang('Search'),
10036
            false,
10037
            [
10038
                'aria-label' => get_lang('Search'),
10039
            ]
10040
        );
10041
10042
        if (api_get_configuration_value('allow_exercise_categories')) {
10043
            $manager = new ExerciseCategoryManager();
10044
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
10045
            if (!empty($options)) {
10046
                $form->addSelect(
10047
                    'category_id',
10048
                    get_lang('Category'),
10049
                    $options,
10050
                    ['placeholder' => get_lang('Please select an option')]
10051
                );
10052
            }
10053
        }
10054
10055
        $form->addButtonSearch(get_lang('Search'));
10056
        $return = $form->returnForm();
10057
10058
        $return .= '<ul class="lp_resource">';
10059
        $return .= '<li class="lp_resource_element">';
10060
        $return .= Display::return_icon('new_exercice.png');
10061
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
10062
            get_lang('New test').'</a>';
10063
        $return .= '</li>';
10064
10065
        $previewIcon = Display::return_icon(
10066
            'preview_view.png',
10067
            get_lang('Preview')
10068
        );
10069
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
10070
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10071
10072
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
10073
        $repo = Container::getExerciseRepository();
10074
        $courseEntity = api_get_course_entity();
10075
        $sessionEntity = api_get_session_entity();
10076
        while ($row_quiz = Database::fetch_array($res_quiz)) {
10077
            /** @var CQuiz $exercise */
10078
            $exercise = $repo->find($row_quiz['id']);
10079
            $title = strip_tags(
10080
                api_html_entity_decode($row_quiz['title'])
10081
            );
10082
10083
            $visibility = $exercise->isVisible($courseEntity, $sessionEntity);
10084
            /*$visibility = api_get_item_visibility(
10085
                ['real_id' => $course_id],
10086
                TOOL_QUIZ,
10087
                $row_quiz['iid'],
10088
                $session_id
10089
            );*/
10090
10091
            $link = Display::url(
10092
                $previewIcon,
10093
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
10094
                ['target' => '_blank']
10095
            );
10096
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
10097
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
10098
            $return .= $quizIcon;
10099
            $sessionStar = api_get_session_image(
10100
                $row_quiz['session_id'],
10101
                $userInfo['status']
10102
            );
10103
            $return .= Display::url(
10104
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
10105
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['id'].'&lp_id='.$this->lp_id,
10106
                [
10107
                    'class' => $visibility === false ? 'moved text-muted' : 'moved',
10108
                ]
10109
            );
10110
            $return .= '</li>';
10111
        }
10112
10113
        $return .= '</ul>';
10114
10115
        return $return;
10116
    }
10117
10118
    /**
10119
     * Creates a list with all the links in it.
10120
     *
10121
     * @return string
10122
     */
10123
    public function get_links()
10124
    {
10125
        $selfUrl = api_get_self();
10126
        $courseIdReq = api_get_cidreq();
10127
        $course = api_get_course_info();
10128
        $userInfo = api_get_user_info();
10129
10130
        $course_id = $course['real_id'];
10131
        $tbl_link = Database::get_course_table(TABLE_LINK);
10132
        $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
10133
        $moveEverywhereIcon = Display::return_icon(
10134
            'move_everywhere.png',
10135
            get_lang('Move'),
10136
            [],
10137
            ICON_SIZE_TINY
10138
        );
10139
10140
        $session_id = api_get_session_id();
10141
        $condition_session = api_get_session_condition(
10142
            $session_id,
10143
            true,
10144
            true,
10145
            'link.session_id'
10146
        );
10147
10148
        $sql = "SELECT
10149
                    link.id as link_id,
10150
                    link.title as link_title,
10151
                    link.session_id as link_session_id,
10152
                    link.category_id as category_id,
10153
                    link_category.category_title as category_title
10154
                FROM $tbl_link as link
10155
                LEFT JOIN $linkCategoryTable as link_category
10156
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
10157
                WHERE link.c_id = $course_id $condition_session
10158
                ORDER BY link_category.category_title ASC, link.title ASC";
10159
        $result = Database::query($sql);
10160
        $categorizedLinks = [];
10161
        $categories = [];
10162
10163
        while ($link = Database::fetch_array($result)) {
10164
            if (!$link['category_id']) {
10165
                $link['category_title'] = get_lang('Uncategorized');
10166
            }
10167
            $categories[$link['category_id']] = $link['category_title'];
10168
            $categorizedLinks[$link['category_id']][$link['link_id']] = $link;
10169
        }
10170
10171
        $linksHtmlCode =
10172
            '<script>
10173
            function toggle_tool(tool, id) {
10174
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
10175
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
10176
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10177
                } else {
10178
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
10179
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
10180
                }
10181
            }
10182
        </script>
10183
10184
        <ul class="lp_resource">
10185
            <li class="lp_resource_element">
10186
                '.Display::return_icon('linksnew.gif').'
10187
                <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').'">'.
10188
                get_lang('Add a link').'
10189
                </a>
10190
            </li>';
10191
10192
        foreach ($categorizedLinks as $categoryId => $links) {
10193
            $linkNodes = null;
10194
            foreach ($links as $key => $linkInfo) {
10195
                $title = $linkInfo['link_title'];
10196
                $linkSessionId = $linkInfo['link_session_id'];
10197
10198
                $link = Display::url(
10199
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10200
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
10201
                    ['target' => '_blank']
10202
                );
10203
10204
                if (api_get_item_visibility($course, TOOL_LINK, $key, $session_id) != 2) {
10205
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
10206
                    $linkNodes .=
10207
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
10208
                        <a class="moved" href="#">'.
10209
                            $moveEverywhereIcon.
10210
                        '</a>
10211
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
10212
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
10213
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
10214
                        Security::remove_XSS($title).$sessionStar.$link.
10215
                        '</a>
10216
                    </li>';
10217
                }
10218
            }
10219
            $linksHtmlCode .=
10220
                '<li>
10221
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
10222
                    <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
10223
                    align="absbottom" />
10224
                </a>
10225
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
10226
            </li>
10227
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
10228
        }
10229
        $linksHtmlCode .= '</ul>';
10230
10231
        return $linksHtmlCode;
10232
    }
10233
10234
    /**
10235
     * Creates a list with all the student publications in it.
10236
     *
10237
     * @return string
10238
     */
10239
    public function get_student_publications()
10240
    {
10241
        $return = '<ul class="lp_resource">';
10242
        $return .= '<li class="lp_resource_element">';
10243
        $return .= Display::return_icon('works_new.gif');
10244
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
10245
            get_lang('Add the Assignments tool to the course').'</a>';
10246
        $return .= '</li>';
10247
10248
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
10249
        $works = getWorkListTeacher(0, 100, null, null, null);
10250
        if (!empty($works)) {
10251
            foreach ($works as $work) {
10252
                $link = Display::url(
10253
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10254
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
10255
                    ['target' => '_blank']
10256
                );
10257
10258
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
10259
                $return .= '<a class="moved" href="#">';
10260
                $return .= Display::return_icon(
10261
                    'move_everywhere.png',
10262
                    get_lang('Move'),
10263
                    [],
10264
                    ICON_SIZE_TINY
10265
                );
10266
                $return .= '</a> ';
10267
10268
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
10269
                $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.'">'.
10270
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
10271
                </a>';
10272
10273
                $return .= '</li>';
10274
            }
10275
        }
10276
10277
        $return .= '</ul>';
10278
10279
        return $return;
10280
    }
10281
10282
    /**
10283
     * Creates a list with all the forums in it.
10284
     *
10285
     * @return string
10286
     */
10287
    public function get_forums()
10288
    {
10289
        require_once '../forum/forumfunction.inc.php';
10290
10291
        $forumCategories = get_forum_categories();
10292
        $forumsInNoCategory = get_forums_in_category(0);
10293
        if (!empty($forumsInNoCategory)) {
10294
            $forumCategories = array_merge(
10295
                $forumCategories,
10296
                [
10297
                    [
10298
                        'cat_id' => 0,
10299
                        'session_id' => 0,
10300
                        'visibility' => 1,
10301
                        'cat_comment' => null,
10302
                    ],
10303
                ]
10304
            );
10305
        }
10306
10307
        $forumList = get_forums();
10308
        $a_forums = [];
10309
        foreach ($forumCategories as $forumCategory) {
10310
            // The forums in this category.
10311
            $forumsInCategory = get_forums_in_category($forumCategory->getIid());
10312
            if (!empty($forumsInCategory)) {
10313
                foreach ($forumList as $forum) {
10314
                    $a_forums[] = $forum;
10315
                }
10316
            }
10317
        }
10318
10319
        $return = '<ul class="lp_resource">';
10320
10321
        // First add link
10322
        $return .= '<li class="lp_resource_element">';
10323
        $return .= Display::return_icon('new_forum.png');
10324
        $return .= Display::url(
10325
            get_lang('Create a new forum'),
10326
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
10327
                'action' => 'add',
10328
                'content' => 'forum',
10329
                'lp_id' => $this->lp_id,
10330
            ]),
10331
            ['title' => get_lang('Create a new forum')]
10332
        );
10333
        $return .= '</li>';
10334
10335
        $return .= '<script>
10336
            function toggle_forum(forum_id) {
10337
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
10338
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
10339
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10340
                } else {
10341
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
10342
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
10343
                }
10344
            }
10345
        </script>';
10346
10347
        foreach ($a_forums as $forum) {
10348
            $forumId = $forum->getIid();
10349
            $title = Security::remove_XSS($forum->getForumTitle());
10350
            $link = Display::url(
10351
                Display::return_icon('preview_view.png', get_lang('Preview')),
10352
                api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forumId,
10353
                ['target' => '_blank']
10354
            );
10355
10356
            $return .= '<li class="lp_resource_element" data_id="'.$forumId.'" data_type="'.TOOL_FORUM.'" title="'.$title.'" >';
10357
            $return .= '<a class="moved" href="#">';
10358
            $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10359
            $return .= ' </a>';
10360
            $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
10361
            $return .= '<a onclick="javascript:toggle_forum('.$forumId.');" style="cursor:hand; vertical-align:middle">
10362
                            <img src="'.Display::returnIconPath('add.png').'" id="forum_'.$forumId.'_opener" align="absbottom" />
10363
                        </a>
10364
                        <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">'.
10365
                $title.' '.$link.'</a>';
10366
10367
            $return .= '</li>';
10368
10369
            $return .= '<div style="display:none" id="forum_'.$forumId.'_content">';
10370
            $a_threads = get_threads($forumId);
10371
            if (is_array($a_threads)) {
10372
                foreach ($a_threads as $thread) {
10373
                    $link = Display::url(
10374
                        Display::return_icon('preview_view.png', get_lang('Preview')),
10375
                        api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forum['forum_id'].'&thread='.$thread['thread_id'],
10376
                        ['target' => '_blank']
10377
                    );
10378
10379
                    $return .= '<li class="lp_resource_element" data_id="'.$thread['thread_id'].'" data_type="'.TOOL_THREAD.'" title="'.$thread['thread_title'].'" >';
10380
                    $return .= '&nbsp;<a class="moved" href="#">';
10381
                    $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10382
                    $return .= ' </a>';
10383
                    $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
10384
                    $return .= '<a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_THREAD.'&thread_id='.$thread['thread_id'].'&lp_id='.$this->lp_id.'">'.
10385
                        Security::remove_XSS($thread['thread_title']).' '.$link.'</a>';
10386
                    $return .= '</li>';
10387
                }
10388
            }
10389
            $return .= '</div>';
10390
        }
10391
        $return .= '</ul>';
10392
10393
        return $return;
10394
    }
10395
10396
    /**
10397
     * // TODO: The output encoding should be equal to the system encoding.
10398
     *
10399
     * Exports the learning path as a SCORM package. This is the main function that
10400
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
10401
     * whole thing and returns the zip.
10402
     *
10403
     * This method needs to be called in PHP5, as it will fail with non-adequate
10404
     * XML package (like the ones for PHP4), and it is *not* a static method, so
10405
     * you need to call it on a learnpath object.
10406
     *
10407
     * @TODO The method might be redefined later on in the scorm class itself to avoid
10408
     * creating a SCORM structure if there is one already. However, if the initial SCORM
10409
     * path has been modified, it should use the generic method here below.
10410
     *
10411
     * @return string Returns the zip package string, or null if error
10412
     */
10413
    public function scormExport()
10414
    {
10415
        api_set_more_memory_and_time_limits();
10416
10417
        $_course = api_get_course_info();
10418
        $course_id = $_course['real_id'];
10419
        // Create the zip handler (this will remain available throughout the method).
10420
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
10421
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
10422
        $temp_dir_short = uniqid('scorm_export', true);
10423
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
10424
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
10425
        $zip_folder = new PclZip($temp_zip_file);
10426
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
10427
        $root_path = $main_path = api_get_path(SYS_PATH);
10428
        $files_cleanup = [];
10429
10430
        // Place to temporarily stash the zip file.
10431
        // create the temp dir if it doesn't exist
10432
        // or do a cleanup before creating the zip file.
10433
        if (!is_dir($temp_zip_dir)) {
10434
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
10435
        } else {
10436
            // Cleanup: Check the temp dir for old files and delete them.
10437
            $handle = opendir($temp_zip_dir);
10438
            while (false !== ($file = readdir($handle))) {
10439
                if ($file != '.' && $file != '..') {
10440
                    unlink("$temp_zip_dir/$file");
10441
                }
10442
            }
10443
            closedir($handle);
10444
        }
10445
        $zip_files = $zip_files_abs = $zip_files_dist = [];
10446
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
10447
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
10448
        ) {
10449
            // Remove the possible . at the end of the path.
10450
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
10451
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
10452
            mkdir(
10453
                $dest_path_to_scorm_folder,
10454
                api_get_permissions_for_new_directories(),
10455
                true
10456
            );
10457
            copyr(
10458
                $current_course_path.'/scorm/'.$this->path,
10459
                $dest_path_to_scorm_folder,
10460
                ['imsmanifest'],
10461
                $zip_files
10462
            );
10463
        }
10464
10465
        // Build a dummy imsmanifest structure.
10466
        // Do not add to the zip yet (we still need it).
10467
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
10468
        // Aggregation Model official document, section "2.3 Content Packaging".
10469
        // We are going to build a UTF-8 encoded manifest.
10470
        // Later we will recode it to the desired (and supported) encoding.
10471
        $xmldoc = new DOMDocument('1.0');
10472
        $root = $xmldoc->createElement('manifest');
10473
        $root->setAttribute('identifier', 'SingleCourseManifest');
10474
        $root->setAttribute('version', '1.1');
10475
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
10476
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
10477
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
10478
        $root->setAttribute(
10479
            'xsi:schemaLocation',
10480
            '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'
10481
        );
10482
        // Build mandatory sub-root container elements.
10483
        $metadata = $xmldoc->createElement('metadata');
10484
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
10485
        $metadata->appendChild($md_schema);
10486
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
10487
        $metadata->appendChild($md_schemaversion);
10488
        $root->appendChild($metadata);
10489
10490
        $organizations = $xmldoc->createElement('organizations');
10491
        $resources = $xmldoc->createElement('resources');
10492
10493
        // Build the only organization we will use in building our learnpaths.
10494
        $organizations->setAttribute('default', 'chamilo_scorm_export');
10495
        $organization = $xmldoc->createElement('organization');
10496
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
10497
        // To set the title of the SCORM entity (=organization), we take the name given
10498
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
10499
        // learning path charset) as it is the encoding that defines how it is stored
10500
        // in the database. Then we convert it to HTML entities again as the "&" character
10501
        // alone is not authorized in XML (must be &amp;).
10502
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
10503
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
10504
        $organization->appendChild($org_title);
10505
        $folder_name = 'document';
10506
10507
        // Removes the learning_path/scorm_folder path when exporting see #4841
10508
        $path_to_remove = '';
10509
        $path_to_replace = '';
10510
        $result = $this->generate_lp_folder($_course);
10511
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
10512
            $path_to_remove = 'document'.$result['dir'];
10513
            $path_to_replace = $folder_name.'/';
10514
        }
10515
10516
        // Fixes chamilo scorm exports
10517
        if ($this->ref === 'chamilo_scorm_export') {
10518
            $path_to_remove = 'scorm/'.$this->path.'/document/';
10519
        }
10520
10521
        // For each element, add it to the imsmanifest structure, then add it to the zip.
10522
        $link_updates = [];
10523
        $links_to_create = [];
10524
        foreach ($this->ordered_items as $index => $itemId) {
10525
            /** @var learnpathItem $item */
10526
            $item = $this->items[$itemId];
10527
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
10528
                // Get included documents from this item.
10529
                if ($item->type === 'sco') {
10530
                    $inc_docs = $item->get_resources_from_source(
10531
                        null,
10532
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
10533
                    );
10534
                } else {
10535
                    $inc_docs = $item->get_resources_from_source();
10536
                }
10537
10538
                // Give a child element <item> to the <organization> element.
10539
                $my_item_id = $item->get_id();
10540
                $my_item = $xmldoc->createElement('item');
10541
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
10542
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
10543
                $my_item->setAttribute('isvisible', 'true');
10544
                // Give a child element <title> to the <item> element.
10545
                $my_title = $xmldoc->createElement(
10546
                    'title',
10547
                    htmlspecialchars(
10548
                        api_utf8_encode($item->get_title()),
10549
                        ENT_QUOTES,
10550
                        'UTF-8'
10551
                    )
10552
                );
10553
                $my_item->appendChild($my_title);
10554
                // Give a child element <adlcp:prerequisites> to the <item> element.
10555
                $my_prereqs = $xmldoc->createElement(
10556
                    'adlcp:prerequisites',
10557
                    $this->get_scorm_prereq_string($my_item_id)
10558
                );
10559
                $my_prereqs->setAttribute('type', 'aicc_script');
10560
                $my_item->appendChild($my_prereqs);
10561
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
10562
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
10563
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
10564
                //$xmldoc->createElement('adlcp:timelimitaction','');
10565
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
10566
                //$xmldoc->createElement('adlcp:datafromlms','');
10567
                // Give a child element <adlcp:masteryscore> to the <item> element.
10568
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10569
                $my_item->appendChild($my_masteryscore);
10570
10571
                // Attach this item to the organization element or hits parent if there is one.
10572
                if (!empty($item->parent) && $item->parent != 0) {
10573
                    $children = $organization->childNodes;
10574
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
10575
                    if (is_object($possible_parent)) {
10576
                        $possible_parent->appendChild($my_item);
10577
                    } else {
10578
                        if ($this->debug > 0) {
10579
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
10580
                        }
10581
                    }
10582
                } else {
10583
                    if ($this->debug > 0) {
10584
                        error_log('No parent');
10585
                    }
10586
                    $organization->appendChild($my_item);
10587
                }
10588
10589
                // Get the path of the file(s) from the course directory root.
10590
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
10591
                $my_xml_file_path = $my_file_path;
10592
                if (!empty($path_to_remove)) {
10593
                    // From docs
10594
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
10595
10596
                    // From quiz
10597
                    if ($this->ref === 'chamilo_scorm_export') {
10598
                        $path_to_remove = 'scorm/'.$this->path.'/';
10599
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
10600
                    }
10601
                }
10602
10603
                $my_sub_dir = dirname($my_file_path);
10604
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10605
                $my_xml_sub_dir = $my_sub_dir;
10606
                // Give a <resource> child to the <resources> element
10607
                $my_resource = $xmldoc->createElement('resource');
10608
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10609
                $my_resource->setAttribute('type', 'webcontent');
10610
                $my_resource->setAttribute('href', $my_xml_file_path);
10611
                // adlcp:scormtype can be either 'sco' or 'asset'.
10612
                if ($item->type === 'sco') {
10613
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
10614
                } else {
10615
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
10616
                }
10617
                // xml:base is the base directory to find the files declared in this resource.
10618
                $my_resource->setAttribute('xml:base', '');
10619
                // Give a <file> child to the <resource> element.
10620
                $my_file = $xmldoc->createElement('file');
10621
                $my_file->setAttribute('href', $my_xml_file_path);
10622
                $my_resource->appendChild($my_file);
10623
10624
                // Dependency to other files - not yet supported.
10625
                $i = 1;
10626
                if ($inc_docs) {
10627
                    foreach ($inc_docs as $doc_info) {
10628
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
10629
                            continue;
10630
                        }
10631
                        $my_dep = $xmldoc->createElement('resource');
10632
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
10633
                        $my_dep->setAttribute('identifier', $res_id);
10634
                        $my_dep->setAttribute('type', 'webcontent');
10635
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
10636
                        $my_dep_file = $xmldoc->createElement('file');
10637
                        // Check type of URL.
10638
                        if ($doc_info[1] == 'remote') {
10639
                            // Remote file. Save url as is.
10640
                            $my_dep_file->setAttribute('href', $doc_info[0]);
10641
                            $my_dep->setAttribute('xml:base', '');
10642
                        } elseif ($doc_info[1] === 'local') {
10643
                            switch ($doc_info[2]) {
10644
                                case 'url':
10645
                                    // Local URL - save path as url for now, don't zip file.
10646
                                    $abs_path = api_get_path(SYS_PATH).
10647
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10648
                                    $current_dir = dirname($abs_path);
10649
                                    $current_dir = str_replace('\\', '/', $current_dir);
10650
                                    $file_path = realpath($abs_path);
10651
                                    $file_path = str_replace('\\', '/', $file_path);
10652
                                    $my_dep_file->setAttribute('href', $file_path);
10653
                                    $my_dep->setAttribute('xml:base', '');
10654
                                    if (strstr($file_path, $main_path) !== false) {
10655
                                        // The calculated real path is really inside Chamilo's root path.
10656
                                        // Reduce file path to what's under the DocumentRoot.
10657
                                        $replace = $file_path;
10658
                                        $file_path = substr($file_path, strlen($root_path) - 1);
10659
                                        $destinationFile = $file_path;
10660
10661
                                        if (strstr($file_path, 'upload/users') !== false) {
10662
                                            $pos = strpos($file_path, 'my_files/');
10663
                                            if ($pos !== false) {
10664
                                                $onlyDirectory = str_replace(
10665
                                                    'upload/users/',
10666
                                                    '',
10667
                                                    substr($file_path, $pos, strlen($file_path))
10668
                                                );
10669
                                            }
10670
                                            $replace = $onlyDirectory;
10671
                                            $destinationFile = $replace;
10672
                                        }
10673
                                        $zip_files_abs[] = $file_path;
10674
                                        $link_updates[$my_file_path][] = [
10675
                                            'orig' => $doc_info[0],
10676
                                            'dest' => $destinationFile,
10677
                                            'replace' => $replace,
10678
                                        ];
10679
                                        $my_dep_file->setAttribute('href', $file_path);
10680
                                        $my_dep->setAttribute('xml:base', '');
10681
                                    } elseif (empty($file_path)) {
10682
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
10683
                                        $file_path = str_replace('//', '/', $file_path);
10684
                                        if (file_exists($file_path)) {
10685
                                            // We get the relative path.
10686
                                            $file_path = substr($file_path, strlen($current_dir));
10687
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
10688
                                            $link_updates[$my_file_path][] = [
10689
                                                'orig' => $doc_info[0],
10690
                                                'dest' => $file_path,
10691
                                            ];
10692
                                            $my_dep_file->setAttribute('href', $file_path);
10693
                                            $my_dep->setAttribute('xml:base', '');
10694
                                        }
10695
                                    }
10696
                                    break;
10697
                                case 'abs':
10698
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
10699
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
10700
                                    $my_dep->setAttribute('xml:base', '');
10701
10702
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
10703
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
10704
                                    $abs_img_path_without_subdir = $doc_info[0];
10705
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
10706
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
10707
                                    if ($pos === 0) {
10708
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
10709
                                    }
10710
10711
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
10712
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
10713
10714
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
10715
                                    $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
10716
                                    // Check if the current document is in that path.
10717
                                    if (strstr($file_path, $cur_path) !== false) {
10718
                                        $destinationFile = substr($file_path, strlen($cur_path));
10719
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
10720
10721
                                        $fileToTest = $cur_path.$my_file_path;
10722
                                        if (!empty($path_to_remove)) {
10723
                                            $fileToTest = str_replace(
10724
                                                $path_to_remove.'/',
10725
                                                $path_to_replace,
10726
                                                $cur_path.$my_file_path
10727
                                            );
10728
                                        }
10729
10730
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
10731
10732
                                        // Put the current document in the zip (this array is the array
10733
                                        // that will manage documents already in the course folder - relative).
10734
                                        $zip_files[] = $filePathNoCoursePart;
10735
                                        // Update the links to the current document in the
10736
                                        // containing document (make them relative).
10737
                                        $link_updates[$my_file_path][] = [
10738
                                            'orig' => $doc_info[0],
10739
                                            'dest' => $destinationFile,
10740
                                            'replace' => $relative_path,
10741
                                        ];
10742
10743
                                        $my_dep_file->setAttribute('href', $file_path);
10744
                                        $my_dep->setAttribute('xml:base', '');
10745
                                    } elseif (strstr($file_path, $main_path) !== false) {
10746
                                        // The calculated real path is really inside Chamilo's root path.
10747
                                        // Reduce file path to what's under the DocumentRoot.
10748
                                        $file_path = substr($file_path, strlen($root_path));
10749
                                        $zip_files_abs[] = $file_path;
10750
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10751
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
10752
                                        $my_dep->setAttribute('xml:base', '');
10753
                                    } elseif (empty($file_path)) {
10754
                                        // Probably this is an image inside "/main" directory
10755
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
10756
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10757
10758
                                        if (file_exists($file_path)) {
10759
                                            if (strstr($file_path, 'main/default_course_document') !== false) {
10760
                                                // We get the relative path.
10761
                                                $pos = strpos($file_path, 'main/default_course_document/');
10762
                                                if ($pos !== false) {
10763
                                                    $onlyDirectory = str_replace(
10764
                                                        'main/default_course_document/',
10765
                                                        '',
10766
                                                        substr($file_path, $pos, strlen($file_path))
10767
                                                    );
10768
                                                }
10769
10770
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
10771
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
10772
                                                $zip_files_abs[] = $fileAbs;
10773
                                                $link_updates[$my_file_path][] = [
10774
                                                    'orig' => $doc_info[0],
10775
                                                    'dest' => $destinationFile,
10776
                                                ];
10777
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
10778
                                                $my_dep->setAttribute('xml:base', '');
10779
                                            }
10780
                                        }
10781
                                    }
10782
                                    break;
10783
                                case 'rel':
10784
                                    // Path relative to the current document.
10785
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
10786
                                    if (substr($doc_info[0], 0, 2) === '..') {
10787
                                        // Relative path going up.
10788
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
10789
                                        $current_dir = str_replace('\\', '/', $current_dir);
10790
                                        $file_path = realpath($current_dir.$doc_info[0]);
10791
                                        $file_path = str_replace('\\', '/', $file_path);
10792
                                        if (strstr($file_path, $main_path) !== false) {
10793
                                            // The calculated real path is really inside Chamilo's root path.
10794
                                            // Reduce file path to what's under the DocumentRoot.
10795
                                            $file_path = substr($file_path, strlen($root_path));
10796
                                            $zip_files_abs[] = $file_path;
10797
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10798
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
10799
                                            $my_dep->setAttribute('xml:base', '');
10800
                                        }
10801
                                    } else {
10802
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
10803
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
10804
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
10805
                                    }
10806
                                    break;
10807
                                default:
10808
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
10809
                                    $my_dep->setAttribute('xml:base', '');
10810
                                    break;
10811
                            }
10812
                        }
10813
                        $my_dep->appendChild($my_dep_file);
10814
                        $resources->appendChild($my_dep);
10815
                        $dependency = $xmldoc->createElement('dependency');
10816
                        $dependency->setAttribute('identifierref', $res_id);
10817
                        $my_resource->appendChild($dependency);
10818
                        $i++;
10819
                    }
10820
                }
10821
                $resources->appendChild($my_resource);
10822
                $zip_files[] = $my_file_path;
10823
            } else {
10824
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
10825
                switch ($item->type) {
10826
                    case TOOL_LINK:
10827
                        $my_item = $xmldoc->createElement('item');
10828
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
10829
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
10830
                        $my_item->setAttribute('isvisible', 'true');
10831
                        // Give a child element <title> to the <item> element.
10832
                        $my_title = $xmldoc->createElement(
10833
                            'title',
10834
                            htmlspecialchars(
10835
                                api_utf8_encode($item->get_title()),
10836
                                ENT_QUOTES,
10837
                                'UTF-8'
10838
                            )
10839
                        );
10840
                        $my_item->appendChild($my_title);
10841
                        // Give a child element <adlcp:prerequisites> to the <item> element.
10842
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
10843
                        $my_prereqs->setAttribute('type', 'aicc_script');
10844
                        $my_item->appendChild($my_prereqs);
10845
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
10846
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
10847
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
10848
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
10849
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
10850
                        //$xmldoc->createElement('adlcp:datafromlms', '');
10851
                        // Give a child element <adlcp:masteryscore> to the <item> element.
10852
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10853
                        $my_item->appendChild($my_masteryscore);
10854
10855
                        // Attach this item to the organization element or its parent if there is one.
10856
                        if (!empty($item->parent) && $item->parent != 0) {
10857
                            $children = $organization->childNodes;
10858
                            for ($i = 0; $i < $children->length; $i++) {
10859
                                $item_temp = $children->item($i);
10860
                                if ($item_temp->nodeName == 'item') {
10861
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
10862
                                        $item_temp->appendChild($my_item);
10863
                                    }
10864
                                }
10865
                            }
10866
                        } else {
10867
                            $organization->appendChild($my_item);
10868
                        }
10869
10870
                        $my_file_path = 'link_'.$item->get_id().'.html';
10871
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
10872
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
10873
                        $rs = Database::query($sql);
10874
                        if ($link = Database::fetch_array($rs)) {
10875
                            $url = $link['url'];
10876
                            $title = stripslashes($link['title']);
10877
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
10878
                            $my_xml_file_path = $my_file_path;
10879
                            $my_sub_dir = dirname($my_file_path);
10880
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10881
                            $my_xml_sub_dir = $my_sub_dir;
10882
                            // Give a <resource> child to the <resources> element.
10883
                            $my_resource = $xmldoc->createElement('resource');
10884
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10885
                            $my_resource->setAttribute('type', 'webcontent');
10886
                            $my_resource->setAttribute('href', $my_xml_file_path);
10887
                            // adlcp:scormtype can be either 'sco' or 'asset'.
10888
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
10889
                            // xml:base is the base directory to find the files declared in this resource.
10890
                            $my_resource->setAttribute('xml:base', '');
10891
                            // give a <file> child to the <resource> element.
10892
                            $my_file = $xmldoc->createElement('file');
10893
                            $my_file->setAttribute('href', $my_xml_file_path);
10894
                            $my_resource->appendChild($my_file);
10895
                            $resources->appendChild($my_resource);
10896
                        }
10897
                        break;
10898
                    case TOOL_QUIZ:
10899
                        $exe_id = $item->path;
10900
                        // Should be using ref when everything will be cleaned up in this regard.
10901
                        $exe = new Exercise();
10902
                        $exe->read($exe_id);
10903
                        $my_item = $xmldoc->createElement('item');
10904
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
10905
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
10906
                        $my_item->setAttribute('isvisible', 'true');
10907
                        // Give a child element <title> to the <item> element.
10908
                        $my_title = $xmldoc->createElement(
10909
                            'title',
10910
                            htmlspecialchars(
10911
                                api_utf8_encode($item->get_title()),
10912
                                ENT_QUOTES,
10913
                                'UTF-8'
10914
                            )
10915
                        );
10916
                        $my_item->appendChild($my_title);
10917
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
10918
                        $my_item->appendChild($my_max_score);
10919
                        // Give a child element <adlcp:prerequisites> to the <item> element.
10920
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
10921
                        $my_prereqs->setAttribute('type', 'aicc_script');
10922
                        $my_item->appendChild($my_prereqs);
10923
                        // Give a child element <adlcp:masteryscore> to the <item> element.
10924
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10925
                        $my_item->appendChild($my_masteryscore);
10926
10927
                        // Attach this item to the organization element or hits parent if there is one.
10928
                        if (!empty($item->parent) && $item->parent != 0) {
10929
                            $children = $organization->childNodes;
10930
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
10931
                            if ($possible_parent) {
10932
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
10933
                                    $possible_parent->appendChild($my_item);
10934
                                }
10935
                            }
10936
                        } else {
10937
                            $organization->appendChild($my_item);
10938
                        }
10939
10940
                        // Get the path of the file(s) from the course directory root
10941
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
10942
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
10943
                        // Write the contents of the exported exercise into a (big) html file
10944
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
10945
                        $scormExercise = new ScormExercise($exe, true);
10946
                        $contents = $scormExercise->export();
10947
10948
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
10949
                        $res = file_put_contents($tmp_file_path, $contents);
10950
                        if ($res === false) {
10951
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
10952
                        }
10953
                        $files_cleanup[] = $tmp_file_path;
10954
                        $my_xml_file_path = $my_file_path;
10955
                        $my_sub_dir = dirname($my_file_path);
10956
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10957
                        $my_xml_sub_dir = $my_sub_dir;
10958
                        // Give a <resource> child to the <resources> element.
10959
                        $my_resource = $xmldoc->createElement('resource');
10960
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10961
                        $my_resource->setAttribute('type', 'webcontent');
10962
                        $my_resource->setAttribute('href', $my_xml_file_path);
10963
                        // adlcp:scormtype can be either 'sco' or 'asset'.
10964
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
10965
                        // xml:base is the base directory to find the files declared in this resource.
10966
                        $my_resource->setAttribute('xml:base', '');
10967
                        // Give a <file> child to the <resource> element.
10968
                        $my_file = $xmldoc->createElement('file');
10969
                        $my_file->setAttribute('href', $my_xml_file_path);
10970
                        $my_resource->appendChild($my_file);
10971
10972
                        // Get included docs.
10973
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
10974
10975
                        // Dependency to other files - not yet supported.
10976
                        $i = 1;
10977
                        foreach ($inc_docs as $doc_info) {
10978
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
10979
                                continue;
10980
                            }
10981
                            $my_dep = $xmldoc->createElement('resource');
10982
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
10983
                            $my_dep->setAttribute('identifier', $res_id);
10984
                            $my_dep->setAttribute('type', 'webcontent');
10985
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
10986
                            $my_dep_file = $xmldoc->createElement('file');
10987
                            // Check type of URL.
10988
                            if ($doc_info[1] == 'remote') {
10989
                                // Remote file. Save url as is.
10990
                                $my_dep_file->setAttribute('href', $doc_info[0]);
10991
                                $my_dep->setAttribute('xml:base', '');
10992
                            } elseif ($doc_info[1] == 'local') {
10993
                                switch ($doc_info[2]) {
10994
                                    case 'url': // Local URL - save path as url for now, don't zip file.
10995
                                        // Save file but as local file (retrieve from URL).
10996
                                        $abs_path = api_get_path(SYS_PATH).
10997
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10998
                                        $current_dir = dirname($abs_path);
10999
                                        $current_dir = str_replace('\\', '/', $current_dir);
11000
                                        $file_path = realpath($abs_path);
11001
                                        $file_path = str_replace('\\', '/', $file_path);
11002
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11003
                                        $my_dep->setAttribute('xml:base', '');
11004
                                        if (strstr($file_path, $main_path) !== false) {
11005
                                            // The calculated real path is really inside the chamilo root path.
11006
                                            // Reduce file path to what's under the DocumentRoot.
11007
                                            $file_path = substr($file_path, strlen($root_path));
11008
                                            $zip_files_abs[] = $file_path;
11009
                                            $link_updates[$my_file_path][] = [
11010
                                                'orig' => $doc_info[0],
11011
                                                'dest' => 'document/'.$file_path,
11012
                                            ];
11013
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11014
                                            $my_dep->setAttribute('xml:base', '');
11015
                                        } elseif (empty($file_path)) {
11016
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11017
                                            $file_path = str_replace('//', '/', $file_path);
11018
                                            if (file_exists($file_path)) {
11019
                                                $file_path = substr($file_path, strlen($current_dir));
11020
                                                // We get the relative path.
11021
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11022
                                                $link_updates[$my_file_path][] = [
11023
                                                    'orig' => $doc_info[0],
11024
                                                    'dest' => 'document/'.$file_path,
11025
                                                ];
11026
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11027
                                                $my_dep->setAttribute('xml:base', '');
11028
                                            }
11029
                                        }
11030
                                        break;
11031
                                    case 'abs':
11032
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11033
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11034
                                        $current_dir = str_replace('\\', '/', $current_dir);
11035
                                        $file_path = realpath($doc_info[0]);
11036
                                        $file_path = str_replace('\\', '/', $file_path);
11037
                                        $my_dep_file->setAttribute('href', $file_path);
11038
                                        $my_dep->setAttribute('xml:base', '');
11039
11040
                                        if (strstr($file_path, $main_path) !== false) {
11041
                                            // The calculated real path is really inside the chamilo root path.
11042
                                            // Reduce file path to what's under the DocumentRoot.
11043
                                            $file_path = substr($file_path, strlen($root_path));
11044
                                            $zip_files_abs[] = $file_path;
11045
                                            $link_updates[$my_file_path][] = [
11046
                                                'orig' => $doc_info[0],
11047
                                                'dest' => $file_path,
11048
                                            ];
11049
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11050
                                            $my_dep->setAttribute('xml:base', '');
11051
                                        } elseif (empty($file_path)) {
11052
                                            $docSysPartPath = str_replace(
11053
                                                api_get_path(REL_COURSE_PATH),
11054
                                                '',
11055
                                                $doc_info[0]
11056
                                            );
11057
11058
                                            $docSysPartPathNoCourseCode = str_replace(
11059
                                                $_course['directory'].'/',
11060
                                                '',
11061
                                                $docSysPartPath
11062
                                            );
11063
11064
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
11065
                                            if (file_exists($docSysPath)) {
11066
                                                $file_path = $docSysPartPathNoCourseCode;
11067
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11068
                                                $link_updates[$my_file_path][] = [
11069
                                                    'orig' => $doc_info[0],
11070
                                                    'dest' => $file_path,
11071
                                                ];
11072
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11073
                                                $my_dep->setAttribute('xml:base', '');
11074
                                            }
11075
                                        }
11076
                                        break;
11077
                                    case 'rel':
11078
                                        // Path relative to the current document. Save xml:base as current document's
11079
                                        // directory and save file in zip as subdir.file_path
11080
                                        if (substr($doc_info[0], 0, 2) === '..') {
11081
                                            // Relative path going up.
11082
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11083
                                            $current_dir = str_replace('\\', '/', $current_dir);
11084
                                            $file_path = realpath($current_dir.$doc_info[0]);
11085
                                            $file_path = str_replace('\\', '/', $file_path);
11086
                                            if (strstr($file_path, $main_path) !== false) {
11087
                                                // The calculated real path is really inside Chamilo's root path.
11088
                                                // Reduce file path to what's under the DocumentRoot.
11089
11090
                                                $file_path = substr($file_path, strlen($root_path));
11091
                                                $file_path_dest = $file_path;
11092
11093
                                                // File path is courses/CHAMILO/document/....
11094
                                                $info_file_path = explode('/', $file_path);
11095
                                                if ($info_file_path[0] == 'courses') {
11096
                                                    // Add character "/" in file path.
11097
                                                    $file_path_dest = 'document/'.$file_path;
11098
                                                }
11099
                                                $zip_files_abs[] = $file_path;
11100
11101
                                                $link_updates[$my_file_path][] = [
11102
                                                    'orig' => $doc_info[0],
11103
                                                    'dest' => $file_path_dest,
11104
                                                ];
11105
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11106
                                                $my_dep->setAttribute('xml:base', '');
11107
                                            }
11108
                                        } else {
11109
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11110
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
11111
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11112
                                        }
11113
                                        break;
11114
                                    default:
11115
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
11116
                                        $my_dep->setAttribute('xml:base', '');
11117
                                        break;
11118
                                }
11119
                            }
11120
                            $my_dep->appendChild($my_dep_file);
11121
                            $resources->appendChild($my_dep);
11122
                            $dependency = $xmldoc->createElement('dependency');
11123
                            $dependency->setAttribute('identifierref', $res_id);
11124
                            $my_resource->appendChild($dependency);
11125
                            $i++;
11126
                        }
11127
                        $resources->appendChild($my_resource);
11128
                        $zip_files[] = $my_file_path;
11129
                        break;
11130
                    default:
11131
                        // Get the path of the file(s) from the course directory root
11132
                        $my_file_path = 'non_exportable.html';
11133
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
11134
                        $my_xml_file_path = $my_file_path;
11135
                        $my_sub_dir = dirname($my_file_path);
11136
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11137
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
11138
                        $my_xml_sub_dir = $my_sub_dir;
11139
                        // Give a <resource> child to the <resources> element.
11140
                        $my_resource = $xmldoc->createElement('resource');
11141
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11142
                        $my_resource->setAttribute('type', 'webcontent');
11143
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
11144
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11145
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
11146
                        // xml:base is the base directory to find the files declared in this resource.
11147
                        $my_resource->setAttribute('xml:base', '');
11148
                        // Give a <file> child to the <resource> element.
11149
                        $my_file = $xmldoc->createElement('file');
11150
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
11151
                        $my_resource->appendChild($my_file);
11152
                        $resources->appendChild($my_resource);
11153
                        break;
11154
                }
11155
            }
11156
        }
11157
        $organizations->appendChild($organization);
11158
        $root->appendChild($organizations);
11159
        $root->appendChild($resources);
11160
        $xmldoc->appendChild($root);
11161
11162
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
11163
11164
        // then add the file to the zip, then destroy the file (this is done automatically).
11165
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
11166
        foreach ($zip_files as $file_path) {
11167
            if (empty($file_path)) {
11168
                continue;
11169
            }
11170
11171
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
11172
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
11173
11174
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
11175
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
11176
            }
11177
11178
            $this->create_path($dest_file);
11179
            @copy($filePath, $dest_file);
11180
11181
            // Check if the file needs a link update.
11182
            if (in_array($file_path, array_keys($link_updates))) {
11183
                $string = file_get_contents($dest_file);
11184
                unlink($dest_file);
11185
                foreach ($link_updates[$file_path] as $old_new) {
11186
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11187
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11188
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11189
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11190
                    if (substr($old_new['dest'], -3) === 'flv' &&
11191
                        substr($old_new['dest'], 0, 5) === 'main/'
11192
                    ) {
11193
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11194
                    } elseif (substr($old_new['dest'], -3) === 'flv' &&
11195
                        substr($old_new['dest'], 0, 6) === 'video/'
11196
                    ) {
11197
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
11198
                    }
11199
11200
                    // Fix to avoid problems with default_course_document
11201
                    if (strpos('main/default_course_document', $old_new['dest']) === false) {
11202
                        $newDestination = $old_new['dest'];
11203
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
11204
                            $newDestination = $old_new['replace'];
11205
                        }
11206
                    } else {
11207
                        $newDestination = str_replace('document/', '', $old_new['dest']);
11208
                    }
11209
                    $string = str_replace($old_new['orig'], $newDestination, $string);
11210
11211
                    // Add files inside the HTMLs
11212
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
11213
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
11214
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
11215
                        copy(
11216
                            $sys_course_path.$new_path,
11217
                            $destinationFile
11218
                        );
11219
                    }
11220
                }
11221
                file_put_contents($dest_file, $string);
11222
            }
11223
11224
            if (file_exists($filePath) && $copyAll) {
11225
                $extension = $this->get_extension($filePath);
11226
                if (in_array($extension, ['html', 'html'])) {
11227
                    $containerOrigin = dirname($filePath);
11228
                    $containerDestination = dirname($dest_file);
11229
11230
                    $finder = new Finder();
11231
                    $finder->files()->in($containerOrigin)
11232
                        ->notName('*_DELETED_*')
11233
                        ->exclude('share_folder')
11234
                        ->exclude('chat_files')
11235
                        ->exclude('certificates')
11236
                    ;
11237
11238
                    if (is_dir($containerOrigin) &&
11239
                        is_dir($containerDestination)
11240
                    ) {
11241
                        $fs = new Filesystem();
11242
                        $fs->mirror(
11243
                            $containerOrigin,
11244
                            $containerDestination,
11245
                            $finder
11246
                        );
11247
                    }
11248
                }
11249
            }
11250
        }
11251
11252
        foreach ($zip_files_abs as $file_path) {
11253
            if (empty($file_path)) {
11254
                continue;
11255
            }
11256
11257
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
11258
                continue;
11259
            }
11260
11261
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
11262
            if (strstr($file_path, 'upload/users') !== false) {
11263
                $pos = strpos($file_path, 'my_files/');
11264
                if ($pos !== false) {
11265
                    $onlyDirectory = str_replace(
11266
                        'upload/users/',
11267
                        '',
11268
                        substr($file_path, $pos, strlen($file_path))
11269
                    );
11270
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
11271
                }
11272
            }
11273
11274
            if (strstr($file_path, 'default_course_document/') !== false) {
11275
                $replace = str_replace('/main', '', $file_path);
11276
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
11277
            }
11278
11279
            if (empty($dest_file)) {
11280
                continue;
11281
            }
11282
11283
            $this->create_path($dest_file);
11284
            copy($main_path.$file_path, $dest_file);
11285
            // Check if the file needs a link update.
11286
            if (in_array($file_path, array_keys($link_updates))) {
11287
                $string = file_get_contents($dest_file);
11288
                unlink($dest_file);
11289
                foreach ($link_updates[$file_path] as $old_new) {
11290
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11291
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11292
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11293
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11294
                    if (substr($old_new['dest'], -3) == 'flv' &&
11295
                        substr($old_new['dest'], 0, 5) == 'main/'
11296
                    ) {
11297
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11298
                    }
11299
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
11300
                }
11301
                file_put_contents($dest_file, $string);
11302
            }
11303
        }
11304
11305
        if (is_array($links_to_create)) {
11306
            foreach ($links_to_create as $file => $link) {
11307
                $content = '<!DOCTYPE html><head>
11308
                            <meta charset="'.api_get_language_isocode().'" />
11309
                            <title>'.$link['title'].'</title>
11310
                            </head>
11311
                            <body dir="'.api_get_text_direction().'">
11312
                            <div style="text-align:center">
11313
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
11314
                            </body>
11315
                            </html>';
11316
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
11317
            }
11318
        }
11319
11320
        // Add non exportable message explanation.
11321
        $lang_not_exportable = get_lang('This learning object or activity is not SCORM compliant. That\'s why it is not exportable.');
11322
        $file_content = '<!DOCTYPE html><head>
11323
                        <meta charset="'.api_get_language_isocode().'" />
11324
                        <title>'.$lang_not_exportable.'</title>
11325
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
11326
                        </head>
11327
                        <body dir="'.api_get_text_direction().'">';
11328
        $file_content .=
11329
            <<<EOD
11330
                    <style>
11331
            .error-message {
11332
                font-family: arial, verdana, helvetica, sans-serif;
11333
                border-width: 1px;
11334
                border-style: solid;
11335
                left: 50%;
11336
                margin: 10px auto;
11337
                min-height: 30px;
11338
                padding: 5px;
11339
                right: 50%;
11340
                width: 500px;
11341
                background-color: #FFD1D1;
11342
                border-color: #FF0000;
11343
                color: #000;
11344
            }
11345
        </style>
11346
    <body>
11347
        <div class="error-message">
11348
            $lang_not_exportable
11349
        </div>
11350
    </body>
11351
</html>
11352
EOD;
11353
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
11354
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
11355
        }
11356
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
11357
11358
        // Add the extra files that go along with a SCORM package.
11359
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
11360
11361
        $fs = new Filesystem();
11362
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
11363
11364
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
11365
        $manifest = @$xmldoc->saveXML();
11366
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
11367
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
11368
        $zip_folder->add(
11369
            $archivePath.'/'.$temp_dir_short,
11370
            PCLZIP_OPT_REMOVE_PATH,
11371
            $archivePath.'/'.$temp_dir_short.'/'
11372
        );
11373
11374
        // Clean possible temporary files.
11375
        foreach ($files_cleanup as $file) {
11376
            $res = unlink($file);
11377
            if ($res === false) {
11378
                error_log(
11379
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
11380
                    0
11381
                );
11382
            }
11383
        }
11384
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
11385
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
11386
    }
11387
11388
    /**
11389
     * @param int $lp_id
11390
     *
11391
     * @return bool
11392
     */
11393
    public function scorm_export_to_pdf($lp_id)
11394
    {
11395
        $lp_id = (int) $lp_id;
11396
        $files_to_export = [];
11397
11398
        $sessionId = api_get_session_id();
11399
        $course_data = api_get_course_info($this->cc);
11400
11401
        if (!empty($course_data)) {
11402
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
11403
            $list = self::get_flat_ordered_items_list($lp_id);
11404
            if (!empty($list)) {
11405
                foreach ($list as $item_id) {
11406
                    $item = $this->items[$item_id];
11407
                    switch ($item->type) {
11408
                        case 'document':
11409
                            // Getting documents from a LP with chamilo documents
11410
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
11411
                            // Try loading document from the base course.
11412
                            if (empty($file_data) && !empty($sessionId)) {
11413
                                $file_data = DocumentManager::get_document_data_by_id(
11414
                                    $item->path,
11415
                                    $this->cc,
11416
                                    false,
11417
                                    0
11418
                                );
11419
                            }
11420
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
11421
                            if (file_exists($file_path)) {
11422
                                $files_to_export[] = [
11423
                                    'title' => $item->get_title(),
11424
                                    'path' => $file_path,
11425
                                ];
11426
                            }
11427
                            break;
11428
                        case 'asset': //commes from a scorm package generated by chamilo
11429
                        case 'sco':
11430
                            $file_path = $scorm_path.'/'.$item->path;
11431
                            if (file_exists($file_path)) {
11432
                                $files_to_export[] = [
11433
                                    'title' => $item->get_title(),
11434
                                    'path' => $file_path,
11435
                                ];
11436
                            }
11437
                            break;
11438
                        case 'dir':
11439
                            $files_to_export[] = [
11440
                                'title' => $item->get_title(),
11441
                                'path' => null,
11442
                            ];
11443
                            break;
11444
                    }
11445
                }
11446
            }
11447
11448
            $pdf = new PDF();
11449
            $result = $pdf->html_to_pdf(
11450
                $files_to_export,
11451
                $this->name,
11452
                $this->cc,
11453
                true,
11454
                true,
11455
                true,
11456
                $this->get_name()
11457
            );
11458
11459
            return $result;
11460
        }
11461
11462
        return false;
11463
    }
11464
11465
    /**
11466
     * Temp function to be moved in main_api or the best place around for this.
11467
     * Creates a file path if it doesn't exist.
11468
     *
11469
     * @param string $path
11470
     */
11471
    public function create_path($path)
11472
    {
11473
        $path_bits = explode('/', dirname($path));
11474
11475
        // IS_WINDOWS_OS has been defined in main_api.lib.php
11476
        $path_built = IS_WINDOWS_OS ? '' : '/';
11477
        foreach ($path_bits as $bit) {
11478
            if (!empty($bit)) {
11479
                $new_path = $path_built.$bit;
11480
                if (is_dir($new_path)) {
11481
                    $path_built = $new_path.'/';
11482
                } else {
11483
                    mkdir($new_path, api_get_permissions_for_new_directories());
11484
                    $path_built = $new_path.'/';
11485
                }
11486
            }
11487
        }
11488
    }
11489
11490
    /**
11491
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
11492
     *
11493
     * @return bool The results of the unlink function, or false if there was no image to start with
11494
     */
11495
    public function delete_lp_image()
11496
    {
11497
        $img = $this->get_preview_image();
11498
        if ($img != '') {
11499
            $del_file = $this->get_preview_image_path(null, 'sys');
11500
            if (isset($del_file) && file_exists($del_file)) {
11501
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
11502
                if (file_exists($del_file_2)) {
11503
                    unlink($del_file_2);
11504
                }
11505
                $this->set_preview_image('');
11506
11507
                return @unlink($del_file);
11508
            }
11509
        }
11510
11511
        return false;
11512
    }
11513
11514
    /**
11515
     * Uploads an author image to the upload/learning_path/images directory.
11516
     *
11517
     * @param array    The image array, coming from the $_FILES superglobal
11518
     *
11519
     * @return bool True on success, false on error
11520
     */
11521
    public function upload_image($image_array)
11522
    {
11523
        if (!empty($image_array['name'])) {
11524
            $upload_ok = process_uploaded_file($image_array);
11525
            $has_attachment = true;
11526
        }
11527
11528
        if ($upload_ok && $has_attachment) {
11529
            $courseDir = api_get_course_path().'/upload/learning_path/images';
11530
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
11531
            $updir = $sys_course_path.$courseDir;
11532
            // Try to add an extension to the file if it hasn't one.
11533
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
11534
11535
            if (filter_extension($new_file_name)) {
11536
                $file_extension = explode('.', $image_array['name']);
11537
                $file_extension = strtolower($file_extension[count($file_extension) - 1]);
11538
                $filename = uniqid('');
11539
                $new_file_name = $filename.'.'.$file_extension;
11540
                $new_path = $updir.'/'.$new_file_name;
11541
11542
                // Resize the image.
11543
                $temp = new Image($image_array['tmp_name']);
11544
                $temp->resize(104);
11545
                $result = $temp->send_image($new_path);
11546
11547
                // Storing the image filename.
11548
                if ($result) {
11549
                    $this->set_preview_image($new_file_name);
11550
11551
                    //Resize to 64px to use on course homepage
11552
                    $temp->resize(64);
11553
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
11554
11555
                    return true;
11556
                }
11557
            }
11558
        }
11559
11560
        return false;
11561
    }
11562
11563
    /**
11564
     * @param int    $lp_id
11565
     * @param string $status
11566
     */
11567
    public function set_autolaunch($lp_id, $status)
11568
    {
11569
        $course_id = api_get_course_int_id();
11570
        $lp_id = (int) $lp_id;
11571
        $status = (int) $status;
11572
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
11573
11574
        // Setting everything to autolaunch = 0
11575
        $attributes['autolaunch'] = 0;
11576
        $where = [
11577
            'session_id = ? AND c_id = ? ' => [
11578
                api_get_session_id(),
11579
                $course_id,
11580
            ],
11581
        ];
11582
        Database::update($lp_table, $attributes, $where);
11583
        if ($status == 1) {
11584
            //Setting my lp_id to autolaunch = 1
11585
            $attributes['autolaunch'] = 1;
11586
            $where = [
11587
                'iid = ? AND session_id = ? AND c_id = ?' => [
11588
                    $lp_id,
11589
                    api_get_session_id(),
11590
                    $course_id,
11591
                ],
11592
            ];
11593
            Database::update($lp_table, $attributes, $where);
11594
        }
11595
    }
11596
11597
    /**
11598
     * Gets previous_item_id for the next element of the lp_item table.
11599
     *
11600
     * @author Isaac flores paz
11601
     *
11602
     * @return int Previous item ID
11603
     */
11604
    public function select_previous_item_id()
11605
    {
11606
        $course_id = api_get_course_int_id();
11607
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11608
11609
        // Get the max order of the items
11610
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
11611
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
11612
        $rs_max_order = Database::query($sql);
11613
        $row_max_order = Database::fetch_object($rs_max_order);
11614
        $max_order = $row_max_order->display_order;
11615
        // Get the previous item ID
11616
        $sql = "SELECT iid as previous FROM $table_lp_item
11617
                WHERE
11618
                    c_id = $course_id AND
11619
                    lp_id = ".$this->lp_id." AND
11620
                    display_order = '$max_order' ";
11621
        $rs_max = Database::query($sql);
11622
        $row_max = Database::fetch_object($rs_max);
11623
11624
        // Return the previous item ID
11625
        return $row_max->previous;
11626
    }
11627
11628
    /**
11629
     * Copies an LP.
11630
     */
11631
    public function copy()
11632
    {
11633
        // Course builder
11634
        $cb = new CourseBuilder();
11635
11636
        //Setting tools that will be copied
11637
        $cb->set_tools_to_build(['learnpaths']);
11638
11639
        //Setting elements that will be copied
11640
        $cb->set_tools_specific_id_list(
11641
            ['learnpaths' => [$this->lp_id]]
11642
        );
11643
11644
        $course = $cb->build();
11645
11646
        //Course restorer
11647
        $course_restorer = new CourseRestorer($course);
11648
        $course_restorer->set_add_text_in_items(true);
11649
        $course_restorer->set_tool_copy_settings(
11650
            ['learnpaths' => ['reset_dates' => true]]
11651
        );
11652
        $course_restorer->restore(
11653
            api_get_course_id(),
11654
            api_get_session_id(),
11655
            false,
11656
            false
11657
        );
11658
    }
11659
11660
    /**
11661
     * Verify document size.
11662
     *
11663
     * @param string $s
11664
     *
11665
     * @return bool
11666
     */
11667
    public static function verify_document_size($s)
11668
    {
11669
        $post_max = ini_get('post_max_size');
11670
        if (substr($post_max, -1, 1) == 'M') {
11671
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
11672
        } elseif (substr($post_max, -1, 1) == 'G') {
11673
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
11674
        }
11675
        $upl_max = ini_get('upload_max_filesize');
11676
        if (substr($upl_max, -1, 1) == 'M') {
11677
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
11678
        } elseif (substr($upl_max, -1, 1) == 'G') {
11679
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
11680
        }
11681
11682
        $repo = Container::getDocumentRepository();
11683
        $documents_total_space = $repo->getTotalSpace(api_get_course_int_id());
11684
11685
        $course_max_space = DocumentManager::get_course_quota();
11686
        $total_size = filesize($s) + $documents_total_space;
11687
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
11688
            return true;
11689
        }
11690
11691
        return false;
11692
    }
11693
11694
    /**
11695
     * Clear LP prerequisites.
11696
     */
11697
    public function clear_prerequisites()
11698
    {
11699
        $course_id = $this->get_course_int_id();
11700
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11701
        $lp_id = $this->get_id();
11702
        // Cleaning prerequisites
11703
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
11704
                WHERE c_id = $course_id AND lp_id = $lp_id";
11705
        Database::query($sql);
11706
11707
        // Cleaning mastery score for exercises
11708
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
11709
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
11710
        Database::query($sql);
11711
    }
11712
11713
    public function set_previous_step_as_prerequisite_for_all_items()
11714
    {
11715
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11716
        $course_id = $this->get_course_int_id();
11717
        $lp_id = $this->get_id();
11718
11719
        if (!empty($this->items)) {
11720
            $previous_item_id = null;
11721
            $previous_item_max = 0;
11722
            $previous_item_type = null;
11723
            $last_item_not_dir = null;
11724
            $last_item_not_dir_type = null;
11725
            $last_item_not_dir_max = null;
11726
11727
            foreach ($this->ordered_items as $itemId) {
11728
                $item = $this->getItem($itemId);
11729
                // if there was a previous item... (otherwise jump to set it)
11730
                if (!empty($previous_item_id)) {
11731
                    $current_item_id = $item->get_id(); //save current id
11732
                    if ($item->get_type() != 'dir') {
11733
                        // Current item is not a folder, so it qualifies to get a prerequisites
11734
                        if ($last_item_not_dir_type == 'quiz') {
11735
                            // if previous is quiz, mark its max score as default score to be achieved
11736
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
11737
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
11738
                            Database::query($sql);
11739
                        }
11740
                        // now simply update the prerequisite to set it to the last non-chapter item
11741
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
11742
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
11743
                        Database::query($sql);
11744
                        // record item as 'non-chapter' reference
11745
                        $last_item_not_dir = $item->get_id();
11746
                        $last_item_not_dir_type = $item->get_type();
11747
                        $last_item_not_dir_max = $item->get_max();
11748
                    }
11749
                } else {
11750
                    if ($item->get_type() != 'dir') {
11751
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
11752
                        $last_item_not_dir = $item->get_id();
11753
                        $last_item_not_dir_type = $item->get_type();
11754
                        $last_item_not_dir_max = $item->get_max();
11755
                    }
11756
                }
11757
                // Saving the item as "previous item" for the next loop
11758
                $previous_item_id = $item->get_id();
11759
                $previous_item_max = $item->get_max();
11760
                $previous_item_type = $item->get_type();
11761
            }
11762
        }
11763
    }
11764
11765
    /**
11766
     * @param array $params
11767
     *
11768
     * @throws \Doctrine\ORM\OptimisticLockException
11769
     *
11770
     * @return int
11771
     */
11772
    public static function createCategory($params)
11773
    {
11774
        $item = new CLpCategory();
11775
        $item->setName($params['name']);
11776
        $item->setCId($params['c_id']);
11777
11778
        $repo = Container::getLpCategoryRepository();
11779
        $em = $repo->getEntityManager();
11780
        $em->persist($item);
11781
        $courseEntity = api_get_course_entity(api_get_course_int_id());
11782
11783
        $repo->addResourceToCourse(
11784
            $item,
11785
            ResourceLink::VISIBILITY_PUBLISHED,
11786
            api_get_user_entity(api_get_user_id()),
11787
            $courseEntity,
11788
            api_get_session_entity(),
11789
            api_get_group_entity()
11790
        );
11791
11792
        $em->flush();
11793
11794
        /*api_item_property_update(
11795
            api_get_course_info(),
11796
            TOOL_LEARNPATH_CATEGORY,
11797
            $item->getId(),
11798
            'visible',
11799
            api_get_user_id()
11800
        );*/
11801
11802
        return $item->getId();
11803
    }
11804
11805
    /**
11806
     * @param array $params
11807
     *
11808
     * @throws \Doctrine\ORM\ORMException
11809
     * @throws \Doctrine\ORM\OptimisticLockException
11810
     * @throws \Doctrine\ORM\TransactionRequiredException
11811
     */
11812
    public static function updateCategory($params)
11813
    {
11814
        $em = Database::getManager();
11815
        /** @var CLpCategory $item */
11816
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
11817
        if ($item) {
11818
            $item->setName($params['name']);
11819
            $em->merge($item);
11820
            $em->flush();
11821
        }
11822
    }
11823
11824
    /**
11825
     * @param int $id
11826
     *
11827
     * @throws \Doctrine\ORM\ORMException
11828
     * @throws \Doctrine\ORM\OptimisticLockException
11829
     * @throws \Doctrine\ORM\TransactionRequiredException
11830
     */
11831
    public static function moveUpCategory($id)
11832
    {
11833
        $id = (int) $id;
11834
        $em = Database::getManager();
11835
        /** @var CLpCategory $item */
11836
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11837
        if ($item) {
11838
            $position = $item->getPosition() - 1;
11839
            $item->setPosition($position);
11840
            $em->persist($item);
11841
            $em->flush();
11842
        }
11843
    }
11844
11845
    /**
11846
     * @param int $id
11847
     *
11848
     * @throws \Doctrine\ORM\ORMException
11849
     * @throws \Doctrine\ORM\OptimisticLockException
11850
     * @throws \Doctrine\ORM\TransactionRequiredException
11851
     */
11852
    public static function moveDownCategory($id)
11853
    {
11854
        $id = (int) $id;
11855
        $em = Database::getManager();
11856
        /** @var CLpCategory $item */
11857
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11858
        if ($item) {
11859
            $position = $item->getPosition() + 1;
11860
            $item->setPosition($position);
11861
            $em->persist($item);
11862
            $em->flush();
11863
        }
11864
    }
11865
11866
    /**
11867
     * @param int $courseId
11868
     *
11869
     * @throws \Doctrine\ORM\Query\QueryException
11870
     *
11871
     * @return int|mixed
11872
     */
11873
    public static function getCountCategories($courseId)
11874
    {
11875
        if (empty($courseId)) {
11876
            return 0;
11877
        }
11878
        $em = Database::getManager();
11879
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
11880
        $query->setParameter('id', $courseId);
11881
11882
        return $query->getSingleScalarResult();
11883
    }
11884
11885
    /**
11886
     * @param int $courseId
11887
     *
11888
     * @return mixed
11889
     */
11890
    public static function getCategories($courseId)
11891
    {
11892
        $em = Database::getManager();
11893
11894
        // Using doctrine extensions
11895
        /** @var SortableRepository $repo */
11896
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
11897
        $items = $repo
11898
            ->getBySortableGroupsQuery(['cId' => $courseId])
11899
            ->getResult();
11900
11901
        return $items;
11902
    }
11903
11904
    /**
11905
     * @param int $id
11906
     *
11907
     * @throws \Doctrine\ORM\ORMException
11908
     * @throws \Doctrine\ORM\OptimisticLockException
11909
     * @throws \Doctrine\ORM\TransactionRequiredException
11910
     *
11911
     * @return CLpCategory
11912
     */
11913
    public static function getCategory($id)
11914
    {
11915
        $id = (int) $id;
11916
        $em = Database::getManager();
11917
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11918
11919
        return $item;
11920
    }
11921
11922
    /**
11923
     * @param int $courseId
11924
     *
11925
     * @return array
11926
     */
11927
    public static function getCategoryByCourse($courseId)
11928
    {
11929
        $em = Database::getManager();
11930
        $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
11931
            ['cId' => $courseId]
11932
        );
11933
11934
        return $items;
11935
    }
11936
11937
    /**
11938
     * @param int $id
11939
     *
11940
     * @throws \Doctrine\ORM\ORMException
11941
     * @throws \Doctrine\ORM\OptimisticLockException
11942
     * @throws \Doctrine\ORM\TransactionRequiredException
11943
     *
11944
     * @return mixed
11945
     */
11946
    public static function deleteCategory($id)
11947
    {
11948
        $em = Database::getManager();
11949
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11950
        if ($item) {
11951
            $courseId = $item->getCId();
11952
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
11953
            $query->setParameter('id', $courseId);
11954
            $query->setParameter('catId', $item->getId());
11955
            $lps = $query->getResult();
11956
11957
            // Setting category = 0.
11958
            if ($lps) {
11959
                foreach ($lps as $lpItem) {
11960
                    $lpItem->setCategoryId(0);
11961
                }
11962
            }
11963
11964
            // Removing category.
11965
            $em->remove($item);
11966
            $em->flush();
11967
11968
            $courseInfo = api_get_course_info_by_id($courseId);
11969
            $sessionId = api_get_session_id();
11970
11971
            // Delete link tool
11972
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
11973
            $link = 'lp/lp_controller.php?cid='.$courseInfo['real_id'].'&sid='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
11974
            // Delete tools
11975
            $sql = "DELETE FROM $tbl_tool
11976
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
11977
            Database::query($sql);
11978
11979
            return true;
11980
        }
11981
11982
        return false;
11983
    }
11984
11985
    /**
11986
     * @param int  $courseId
11987
     * @param bool $addSelectOption
11988
     *
11989
     * @return mixed
11990
     */
11991
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
11992
    {
11993
        $items = self::getCategoryByCourse($courseId);
11994
        $cats = [];
11995
        if ($addSelectOption) {
11996
            $cats = [get_lang('Select a category')];
11997
        }
11998
11999
        if (!empty($items)) {
12000
            foreach ($items as $cat) {
12001
                $cats[$cat->getId()] = $cat->getName();
12002
            }
12003
        }
12004
12005
        return $cats;
12006
    }
12007
12008
    /**
12009
     * @param string $courseCode
12010
     * @param int    $lpId
12011
     * @param int    $user_id
12012
     *
12013
     * @return learnpath
12014
     */
12015
    public static function getLpFromSession($courseCode, $lpId, $user_id)
12016
    {
12017
        $debug = 0;
12018
        $learnPath = null;
12019
        $lpObject = Session::read('lpobject');
12020
        if ($lpObject !== null) {
12021
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
12022
            if ($debug) {
12023
                error_log('getLpFromSession: unserialize');
12024
                error_log('------getLpFromSession------');
12025
                error_log('------unserialize------');
12026
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12027
                error_log("api_get_sessionid: ".api_get_session_id());
12028
            }
12029
        }
12030
12031
        if (!is_object($learnPath)) {
12032
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
12033
            if ($debug) {
12034
                error_log('------getLpFromSession------');
12035
                error_log('getLpFromSession: create new learnpath');
12036
                error_log("create new LP with $courseCode - $lpId - $user_id");
12037
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12038
                error_log("api_get_sessionid: ".api_get_session_id());
12039
            }
12040
        }
12041
12042
        return $learnPath;
12043
    }
12044
12045
    /**
12046
     * @param int $itemId
12047
     *
12048
     * @return learnpathItem|false
12049
     */
12050
    public function getItem($itemId)
12051
    {
12052
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
12053
            return $this->items[$itemId];
12054
        }
12055
12056
        return false;
12057
    }
12058
12059
    /**
12060
     * @return int
12061
     */
12062
    public function getCurrentAttempt()
12063
    {
12064
        $attempt = $this->getItem($this->get_current_item_id());
12065
        if ($attempt) {
12066
            $attemptId = $attempt->get_attempt_id();
12067
12068
            return $attemptId;
12069
        }
12070
12071
        return 0;
12072
    }
12073
12074
    /**
12075
     * @return int
12076
     */
12077
    public function getCategoryId()
12078
    {
12079
        return (int) $this->categoryId;
12080
    }
12081
12082
    /**
12083
     * @param int $categoryId
12084
     *
12085
     * @return bool
12086
     */
12087
    public function setCategoryId($categoryId)
12088
    {
12089
        $this->categoryId = (int) $categoryId;
12090
        $table = Database::get_course_table(TABLE_LP_MAIN);
12091
        $lp_id = $this->get_id();
12092
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
12093
                WHERE iid = $lp_id";
12094
        Database::query($sql);
12095
12096
        return true;
12097
    }
12098
12099
    /**
12100
     * Get whether this is a learning path with the possibility to subscribe
12101
     * users or not.
12102
     *
12103
     * @return int
12104
     */
12105
    public function getSubscribeUsers()
12106
    {
12107
        return $this->subscribeUsers;
12108
    }
12109
12110
    /**
12111
     * Set whether this is a learning path with the possibility to subscribe
12112
     * users or not.
12113
     *
12114
     * @param int $value (0 = false, 1 = true)
12115
     *
12116
     * @return bool
12117
     */
12118
    public function setSubscribeUsers($value)
12119
    {
12120
        $this->subscribeUsers = (int) $value;
12121
        $table = Database::get_course_table(TABLE_LP_MAIN);
12122
        $lp_id = $this->get_id();
12123
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
12124
                WHERE iid = $lp_id";
12125
        Database::query($sql);
12126
12127
        return true;
12128
    }
12129
12130
    /**
12131
     * Calculate the count of stars for a user in this LP
12132
     * This calculation is based on the following rules:
12133
     * - the student gets one star when he gets to 50% of the learning path
12134
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
12135
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
12136
     * - the student gets the final star when the score for the *last* test is >= 80%.
12137
     *
12138
     * @param int $sessionId Optional. The session ID
12139
     *
12140
     * @return int The count of stars
12141
     */
12142
    public function getCalculateStars($sessionId = 0)
12143
    {
12144
        $stars = 0;
12145
        $progress = self::getProgress(
12146
            $this->lp_id,
12147
            $this->user_id,
12148
            $this->course_int_id,
12149
            $sessionId
12150
        );
12151
12152
        if ($progress >= 50) {
12153
            $stars++;
12154
        }
12155
12156
        // Calculate stars chapters evaluation
12157
        $exercisesItems = $this->getExercisesItems();
12158
12159
        if (!empty($exercisesItems)) {
12160
            $totalResult = 0;
12161
12162
            foreach ($exercisesItems as $exerciseItem) {
12163
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12164
                    $this->user_id,
12165
                    $exerciseItem->path,
12166
                    $this->course_int_id,
12167
                    $sessionId,
12168
                    $this->lp_id,
12169
                    $exerciseItem->db_id
12170
                );
12171
12172
                $exerciseResultInfo = end($exerciseResultInfo);
12173
12174
                if (!$exerciseResultInfo) {
12175
                    continue;
12176
                }
12177
12178
                if (!empty($exerciseResultInfo['max_score'])) {
12179
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
12180
                } else {
12181
                    $exerciseResult = 0;
12182
                }
12183
                $totalResult += $exerciseResult;
12184
            }
12185
12186
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
12187
12188
            if ($totalExerciseAverage >= 50) {
12189
                $stars++;
12190
            }
12191
12192
            if ($totalExerciseAverage >= 80) {
12193
                $stars++;
12194
            }
12195
        }
12196
12197
        // Calculate star for final evaluation
12198
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12199
12200
        if (!empty($finalEvaluationItem)) {
12201
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12202
                $this->user_id,
12203
                $finalEvaluationItem->path,
12204
                $this->course_int_id,
12205
                $sessionId,
12206
                $this->lp_id,
12207
                $finalEvaluationItem->db_id
12208
            );
12209
12210
            $evaluationResultInfo = end($evaluationResultInfo);
12211
12212
            if ($evaluationResultInfo) {
12213
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
12214
12215
                if ($evaluationResult >= 80) {
12216
                    $stars++;
12217
                }
12218
            }
12219
        }
12220
12221
        return $stars;
12222
    }
12223
12224
    /**
12225
     * Get the items of exercise type.
12226
     *
12227
     * @return array The items. Otherwise return false
12228
     */
12229
    public function getExercisesItems()
12230
    {
12231
        $exercises = [];
12232
        foreach ($this->items as $item) {
12233
            if ($item->type != 'quiz') {
12234
                continue;
12235
            }
12236
            $exercises[] = $item;
12237
        }
12238
12239
        array_pop($exercises);
12240
12241
        return $exercises;
12242
    }
12243
12244
    /**
12245
     * Get the item of exercise type (evaluation type).
12246
     *
12247
     * @return array The final evaluation. Otherwise return false
12248
     */
12249
    public function getFinalEvaluationItem()
12250
    {
12251
        $exercises = [];
12252
        foreach ($this->items as $item) {
12253
            if ($item->type != 'quiz') {
12254
                continue;
12255
            }
12256
12257
            $exercises[] = $item;
12258
        }
12259
12260
        return array_pop($exercises);
12261
    }
12262
12263
    /**
12264
     * Calculate the total points achieved for the current user in this learning path.
12265
     *
12266
     * @param int $sessionId Optional. The session Id
12267
     *
12268
     * @return int
12269
     */
12270
    public function getCalculateScore($sessionId = 0)
12271
    {
12272
        // Calculate stars chapters evaluation
12273
        $exercisesItems = $this->getExercisesItems();
12274
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12275
        $totalExercisesResult = 0;
12276
        $totalEvaluationResult = 0;
12277
12278
        if ($exercisesItems !== false) {
12279
            foreach ($exercisesItems as $exerciseItem) {
12280
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12281
                    $this->user_id,
12282
                    $exerciseItem->path,
12283
                    $this->course_int_id,
12284
                    $sessionId,
12285
                    $this->lp_id,
12286
                    $exerciseItem->db_id
12287
                );
12288
12289
                $exerciseResultInfo = end($exerciseResultInfo);
12290
12291
                if (!$exerciseResultInfo) {
12292
                    continue;
12293
                }
12294
12295
                $totalExercisesResult += $exerciseResultInfo['score'];
12296
            }
12297
        }
12298
12299
        if (!empty($finalEvaluationItem)) {
12300
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12301
                $this->user_id,
12302
                $finalEvaluationItem->path,
12303
                $this->course_int_id,
12304
                $sessionId,
12305
                $this->lp_id,
12306
                $finalEvaluationItem->db_id
12307
            );
12308
12309
            $evaluationResultInfo = end($evaluationResultInfo);
12310
12311
            if ($evaluationResultInfo) {
12312
                $totalEvaluationResult += $evaluationResultInfo['score'];
12313
            }
12314
        }
12315
12316
        return $totalExercisesResult + $totalEvaluationResult;
12317
    }
12318
12319
    /**
12320
     * Check if URL is not allowed to be show in a iframe.
12321
     *
12322
     * @param string $src
12323
     *
12324
     * @return string
12325
     */
12326
    public function fixBlockedLinks($src)
12327
    {
12328
        $urlInfo = parse_url($src);
12329
12330
        $platformProtocol = 'https';
12331
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
12332
            $platformProtocol = 'http';
12333
        }
12334
12335
        $protocolFixApplied = false;
12336
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
12337
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
12338
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
12339
12340
        if ($platformProtocol != $scheme) {
12341
            Session::write('x_frame_source', $src);
12342
            $src = 'blank.php?error=x_frames_options';
12343
            $protocolFixApplied = true;
12344
        }
12345
12346
        if ($protocolFixApplied == false) {
12347
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
12348
                // Check X-Frame-Options
12349
                $ch = curl_init();
12350
                $options = [
12351
                    CURLOPT_URL => $src,
12352
                    CURLOPT_RETURNTRANSFER => true,
12353
                    CURLOPT_HEADER => true,
12354
                    CURLOPT_FOLLOWLOCATION => true,
12355
                    CURLOPT_ENCODING => "",
12356
                    CURLOPT_AUTOREFERER => true,
12357
                    CURLOPT_CONNECTTIMEOUT => 120,
12358
                    CURLOPT_TIMEOUT => 120,
12359
                    CURLOPT_MAXREDIRS => 10,
12360
                ];
12361
12362
                $proxySettings = api_get_configuration_value('proxy_settings');
12363
                if (!empty($proxySettings) &&
12364
                    isset($proxySettings['curl_setopt_array'])
12365
                ) {
12366
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
12367
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
12368
                }
12369
12370
                curl_setopt_array($ch, $options);
12371
                $response = curl_exec($ch);
12372
                $httpCode = curl_getinfo($ch);
12373
                $headers = substr($response, 0, $httpCode['header_size']);
12374
12375
                $error = false;
12376
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
12377
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
12378
                ) {
12379
                    $error = true;
12380
                }
12381
12382
                if ($error) {
12383
                    Session::write('x_frame_source', $src);
12384
                    $src = 'blank.php?error=x_frames_options';
12385
                }
12386
            }
12387
        }
12388
12389
        return $src;
12390
    }
12391
12392
    /**
12393
     * Check if this LP has a created forum in the basis course.
12394
     *
12395
     * @return bool
12396
     */
12397
    public function lpHasForum()
12398
    {
12399
        $forumTable = Database::get_course_table(TABLE_FORUM);
12400
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12401
12402
        $fakeFrom = "
12403
            $forumTable f
12404
            INNER JOIN $itemProperty ip
12405
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
12406
        ";
12407
12408
        $resultData = Database::select(
12409
            'COUNT(f.iid) AS qty',
12410
            $fakeFrom,
12411
            [
12412
                'where' => [
12413
                    'ip.visibility != ? AND ' => 2,
12414
                    'ip.tool = ? AND ' => TOOL_FORUM,
12415
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12416
                    'f.lp_id = ?' => intval($this->lp_id),
12417
                ],
12418
            ],
12419
            'first'
12420
        );
12421
12422
        return $resultData['qty'] > 0;
12423
    }
12424
12425
    /**
12426
     * Get the forum for this learning path.
12427
     *
12428
     * @param int $sessionId
12429
     *
12430
     * @return array
12431
     */
12432
    public function getForum($sessionId = 0)
12433
    {
12434
        $repo = Container::getForumRepository();
12435
12436
        $course = api_get_course_entity();
12437
        $session = api_get_session_entity($sessionId);
12438
12439
        $qb = $repo->getResourcesByCourse($course, $session);
12440
12441
        return $qb->getQuery()->getResult();
12442
    }
12443
12444
    /**
12445
     * Create a forum for this learning path.
12446
     *
12447
     * @param int $forumCategoryId
12448
     *
12449
     * @return int The forum ID if was created. Otherwise return false
12450
     */
12451
    public function createForum($forumCategoryId)
12452
    {
12453
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
12454
12455
        $forumId = store_forum(
12456
            [
12457
                'lp_id' => $this->lp_id,
12458
                'forum_title' => $this->name,
12459
                'forum_comment' => null,
12460
                'forum_category' => (int) $forumCategoryId,
12461
                'students_can_edit_group' => ['students_can_edit' => 0],
12462
                'allow_new_threads_group' => ['allow_new_threads' => 0],
12463
                'default_view_type_group' => ['default_view_type' => 'flat'],
12464
                'group_forum' => 0,
12465
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
12466
            ],
12467
            [],
12468
            true
12469
        );
12470
12471
        return $forumId;
12472
    }
12473
12474
    /**
12475
     * Get the LP Final Item form.
12476
     *
12477
     * @throws Exception
12478
     * @throws HTML_QuickForm_Error
12479
     *
12480
     * @return string
12481
     */
12482
    public function getFinalItemForm()
12483
    {
12484
        $finalItem = $this->getFinalItem();
12485
        $title = '';
12486
12487
        if ($finalItem) {
12488
            $title = $finalItem->get_title();
12489
            $buttonText = get_lang('Save');
12490
            $content = $this->getSavedFinalItem();
12491
        } else {
12492
            $buttonText = get_lang('Add this document to the course');
12493
            $content = $this->getFinalItemTemplate();
12494
        }
12495
12496
        $courseInfo = api_get_course_info();
12497
        $result = $this->generate_lp_folder($courseInfo);
12498
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
12499
        $relative_prefix = '../../';
12500
12501
        $editorConfig = [
12502
            'ToolbarSet' => 'LearningPathDocuments',
12503
            'Width' => '100%',
12504
            'Height' => '500',
12505
            'FullPage' => true,
12506
            'CreateDocumentDir' => $relative_prefix,
12507
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
12508
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
12509
        ];
12510
12511
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
12512
            'type' => 'document',
12513
            'lp_id' => $this->lp_id,
12514
        ]);
12515
12516
        $form = new FormValidator('final_item', 'POST', $url);
12517
        $form->addText('title', get_lang('Title'));
12518
        $form->addButtonSave($buttonText);
12519
        $form->addHtml(
12520
            Display::return_message(
12521
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
12522
                'normal',
12523
                false
12524
            )
12525
        );
12526
12527
        $renderer = $form->defaultRenderer();
12528
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
12529
12530
        $form->addHtmlEditor(
12531
            'content_lp_certificate',
12532
            null,
12533
            true,
12534
            false,
12535
            $editorConfig,
12536
            true
12537
        );
12538
        $form->addHidden('action', 'add_final_item');
12539
        $form->addHidden('path', Session::read('pathItem'));
12540
        $form->addHidden('previous', $this->get_last());
12541
        $form->setDefaults(
12542
            ['title' => $title, 'content_lp_certificate' => $content]
12543
        );
12544
12545
        if ($form->validate()) {
12546
            $values = $form->exportValues();
12547
            $lastItemId = $this->getLastInFirstLevel();
12548
12549
            if (!$finalItem) {
12550
                $documentId = $this->create_document(
12551
                    $this->course_info,
12552
                    $values['content_lp_certificate'],
12553
                    $values['title']
12554
                );
12555
                $this->add_item(
12556
                    0,
12557
                    $lastItemId,
12558
                    'final_item',
12559
                    $documentId,
12560
                    $values['title'],
12561
                    ''
12562
                );
12563
12564
                Display::addFlash(
12565
                    Display::return_message(get_lang('Added'))
12566
                );
12567
            } else {
12568
                $this->edit_document($this->course_info);
12569
            }
12570
        }
12571
12572
        return $form->returnForm();
12573
    }
12574
12575
    /**
12576
     * Check if the current lp item is first, both, last or none from lp list.
12577
     *
12578
     * @param int $currentItemId
12579
     *
12580
     * @return string
12581
     */
12582
    public function isFirstOrLastItem($currentItemId)
12583
    {
12584
        $lpItemId = [];
12585
        $typeListNotToVerify = self::getChapterTypes();
12586
12587
        // Using get_toc() function instead $this->items because returns the correct order of the items
12588
        foreach ($this->get_toc() as $item) {
12589
            if (!in_array($item['type'], $typeListNotToVerify)) {
12590
                $lpItemId[] = $item['id'];
12591
            }
12592
        }
12593
12594
        $lastLpItemIndex = count($lpItemId) - 1;
12595
        $position = array_search($currentItemId, $lpItemId);
12596
12597
        switch ($position) {
12598
            case 0:
12599
                if (!$lastLpItemIndex) {
12600
                    $answer = 'both';
12601
                    break;
12602
                }
12603
12604
                $answer = 'first';
12605
                break;
12606
            case $lastLpItemIndex:
12607
                $answer = 'last';
12608
                break;
12609
            default:
12610
                $answer = 'none';
12611
        }
12612
12613
        return $answer;
12614
    }
12615
12616
    /**
12617
     * Get whether this is a learning path with the accumulated SCORM time or not.
12618
     *
12619
     * @return int
12620
     */
12621
    public function getAccumulateScormTime()
12622
    {
12623
        return $this->accumulateScormTime;
12624
    }
12625
12626
    /**
12627
     * Set whether this is a learning path with the accumulated SCORM time or not.
12628
     *
12629
     * @param int $value (0 = false, 1 = true)
12630
     *
12631
     * @return bool Always returns true
12632
     */
12633
    public function setAccumulateScormTime($value)
12634
    {
12635
        $this->accumulateScormTime = (int) $value;
12636
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
12637
        $lp_id = $this->get_id();
12638
        $sql = "UPDATE $lp_table
12639
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
12640
                WHERE iid = $lp_id";
12641
        Database::query($sql);
12642
12643
        return true;
12644
    }
12645
12646
    /**
12647
     * Returns an HTML-formatted link to a resource, to incorporate directly into
12648
     * the new learning path tool.
12649
     *
12650
     * The function is a big switch on tool type.
12651
     * In each case, we query the corresponding table for information and build the link
12652
     * with that information.
12653
     *
12654
     * @author Yannick Warnier <[email protected]> - rebranding based on
12655
     * previous work (display_addedresource_link_in_learnpath())
12656
     *
12657
     * @param int $course_id      Course code
12658
     * @param int $learningPathId The learning path ID (in lp table)
12659
     * @param int $id_in_path     the unique index in the items table
12660
     * @param int $lpViewId
12661
     *
12662
     * @return string
12663
     */
12664
    public static function rl_get_resource_link_for_learnpath(
12665
        $course_id,
12666
        $learningPathId,
12667
        $id_in_path,
12668
        $lpViewId
12669
    ) {
12670
        $session_id = api_get_session_id();
12671
        $course_info = api_get_course_info_by_id($course_id);
12672
12673
        $learningPathId = (int) $learningPathId;
12674
        $id_in_path = (int) $id_in_path;
12675
        $lpViewId = (int) $lpViewId;
12676
12677
        $em = Database::getManager();
12678
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
12679
12680
        /** @var CLpItem $rowItem */
12681
        $rowItem = $lpItemRepo->findOneBy([
12682
            'cId' => $course_id,
12683
            'lpId' => $learningPathId,
12684
            'iid' => $id_in_path,
12685
        ]);
12686
12687
        if (!$rowItem) {
12688
            // Try one more time with "id"
12689
            /** @var CLpItem $rowItem */
12690
            $rowItem = $lpItemRepo->findOneBy([
12691
                'cId' => $course_id,
12692
                'lpId' => $learningPathId,
12693
                'id' => $id_in_path,
12694
            ]);
12695
12696
            if (!$rowItem) {
12697
                return -1;
12698
            }
12699
        }
12700
12701
        $type = $rowItem->getItemType();
12702
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
12703
        $main_dir_path = api_get_path(WEB_CODE_PATH);
12704
        //$main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
12705
        $link = '';
12706
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
12707
12708
        switch ($type) {
12709
            case 'dir':
12710
                return $main_dir_path.'lp/blank.php';
12711
            case TOOL_CALENDAR_EVENT:
12712
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
12713
            case TOOL_ANNOUNCEMENT:
12714
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
12715
            case TOOL_LINK:
12716
                $linkInfo = Link::getLinkInfo($id);
12717
                if (isset($linkInfo['url'])) {
12718
                    return $linkInfo['url'];
12719
                }
12720
12721
                return '';
12722
            case TOOL_QUIZ:
12723
                if (empty($id)) {
12724
                    return '';
12725
                }
12726
12727
                // Get the lp_item_view with the highest view_count.
12728
                $learnpathItemViewResult = $em
12729
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
12730
                    ->findBy(
12731
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
12732
                        ['viewCount' => 'DESC'],
12733
                        1
12734
                    );
12735
                /** @var CLpItemView $learnpathItemViewData */
12736
                $learnpathItemViewData = current($learnpathItemViewResult);
12737
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
12738
12739
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
12740
                    .http_build_query([
12741
                        'lp_init' => 1,
12742
                        'learnpath_item_view_id' => $learnpathItemViewId,
12743
                        'learnpath_id' => $learningPathId,
12744
                        'learnpath_item_id' => $id_in_path,
12745
                        'exerciseId' => $id,
12746
                    ]);
12747
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
12748
                /*$TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
12749
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
12750
                $myrow = Database::fetch_array($result);
12751
                $path = $myrow['path'];
12752
12753
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
12754
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
12755
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;*/
12756
            case TOOL_FORUM:
12757
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
12758
            case TOOL_THREAD:
12759
                // forum post
12760
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
12761
                if (empty($id)) {
12762
                    return '';
12763
                }
12764
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
12765
                $result = Database::query($sql);
12766
                $myrow = Database::fetch_array($result);
12767
12768
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
12769
                    .$extraParams;
12770
            case TOOL_POST:
12771
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12772
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
12773
                $myrow = Database::fetch_array($result);
12774
12775
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
12776
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
12777
            case TOOL_READOUT_TEXT:
12778
                return api_get_path(WEB_CODE_PATH).
12779
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
12780
            case TOOL_DOCUMENT:
12781
                $repo = Container::getDocumentRepository();
12782
                $document = $repo->find($rowItem->getPath());
12783
                $file = $repo->getResourceFileUrl($document, [], UrlGeneratorInterface::ABSOLUTE_URL);
12784
12785
                return $file;
12786
12787
                $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...
12788
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v'];
12789
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
12790
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
12791
12792
                $openmethod = 2;
12793
                $officedoc = false;
12794
                Session::write('openmethod', $openmethod);
12795
                Session::write('officedoc', $officedoc);
12796
12797
                if ($showDirectUrl) {
12798
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
12799
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
12800
                        if (Link::isPdfLink($file)) {
12801
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
12802
12803
                            return $pdfUrl;
12804
                        }
12805
                    }
12806
12807
                    return $file;
12808
                }
12809
12810
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
12811
            case TOOL_LP_FINAL_ITEM:
12812
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
12813
                    .$extraParams;
12814
            case 'assignments':
12815
                return $main_dir_path.'work/work.php?'.$extraParams;
12816
            case TOOL_DROPBOX:
12817
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
12818
            case 'introduction_text': //DEPRECATED
12819
                return '';
12820
            case TOOL_COURSE_DESCRIPTION:
12821
                return $main_dir_path.'course_description?'.$extraParams;
12822
            case TOOL_GROUP:
12823
                return $main_dir_path.'group/group.php?'.$extraParams;
12824
            case TOOL_USER:
12825
                return $main_dir_path.'user/user.php?'.$extraParams;
12826
            case TOOL_STUDENTPUBLICATION:
12827
                if (!empty($rowItem->getPath())) {
12828
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
12829
                }
12830
12831
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
12832
        }
12833
12834
        return $link;
12835
    }
12836
12837
    /**
12838
     * Gets the name of a resource (generally used in learnpath when no name is provided).
12839
     *
12840
     * @author Yannick Warnier <[email protected]>
12841
     *
12842
     * @param string $course_code    Course code
12843
     * @param int    $learningPathId
12844
     * @param int    $id_in_path     The resource ID
12845
     *
12846
     * @return string
12847
     */
12848
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
12849
    {
12850
        $_course = api_get_course_info($course_code);
12851
        if (empty($_course)) {
12852
            return '';
12853
        }
12854
        $course_id = $_course['real_id'];
12855
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12856
        $learningPathId = (int) $learningPathId;
12857
        $id_in_path = (int) $id_in_path;
12858
12859
        $sql = "SELECT item_type, title, ref
12860
                FROM $tbl_lp_item
12861
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
12862
        $res_item = Database::query($sql);
12863
12864
        if (Database::num_rows($res_item) < 1) {
12865
            return '';
12866
        }
12867
        $row_item = Database::fetch_array($res_item);
12868
        $type = strtolower($row_item['item_type']);
12869
        $id = $row_item['ref'];
12870
        $output = '';
12871
12872
        switch ($type) {
12873
            case TOOL_CALENDAR_EVENT:
12874
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
12875
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
12876
                $myrow = Database::fetch_array($result);
12877
                $output = $myrow['title'];
12878
                break;
12879
            case TOOL_ANNOUNCEMENT:
12880
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
12881
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
12882
                $myrow = Database::fetch_array($result);
12883
                $output = $myrow['title'];
12884
                break;
12885
            case TOOL_LINK:
12886
                // Doesn't take $target into account.
12887
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
12888
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
12889
                $myrow = Database::fetch_array($result);
12890
                $output = $myrow['title'];
12891
                break;
12892
            case TOOL_QUIZ:
12893
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
12894
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
12895
                $myrow = Database::fetch_array($result);
12896
                $output = $myrow['title'];
12897
                break;
12898
            case TOOL_FORUM:
12899
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
12900
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
12901
                $myrow = Database::fetch_array($result);
12902
                $output = $myrow['forum_name'];
12903
                break;
12904
            case TOOL_THREAD:
12905
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12906
                // Grabbing the title of the post.
12907
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
12908
                $result_title = Database::query($sql_title);
12909
                $myrow_title = Database::fetch_array($result_title);
12910
                $output = $myrow_title['post_title'];
12911
                break;
12912
            case TOOL_POST:
12913
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12914
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
12915
                $result = Database::query($sql);
12916
                $post = Database::fetch_array($result);
12917
                $output = $post['post_title'];
12918
                break;
12919
            case 'dir':
12920
            case TOOL_DOCUMENT:
12921
                $title = $row_item['title'];
12922
                $output = '-';
12923
                if (!empty($title)) {
12924
                    $output = $title;
12925
                }
12926
                break;
12927
            case 'hotpotatoes':
12928
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
12929
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
12930
                $myrow = Database::fetch_array($result);
12931
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
12932
                $last = count($pathname) - 1; // Making a correct name for the link.
12933
                $filename = $pathname[$last]; // Making a correct name for the link.
12934
                $myrow['path'] = rawurlencode($myrow['path']);
12935
                $output = $filename;
12936
                break;
12937
        }
12938
12939
        return stripslashes($output);
12940
    }
12941
12942
    /**
12943
     * Get the parent names for the current item.
12944
     *
12945
     * @param int $newItemId Optional. The item ID
12946
     *
12947
     * @return array
12948
     */
12949
    public function getCurrentItemParentNames($newItemId = 0)
12950
    {
12951
        $newItemId = $newItemId ?: $this->get_current_item_id();
12952
        $return = [];
12953
        $item = $this->getItem($newItemId);
12954
        $parent = $this->getItem($item->get_parent());
12955
12956
        while ($parent) {
12957
            $return[] = $parent->get_title();
12958
            $parent = $this->getItem($parent->get_parent());
12959
        }
12960
12961
        return array_reverse($return);
12962
    }
12963
12964
    /**
12965
     * Reads and process "lp_subscription_settings" setting.
12966
     *
12967
     * @return array
12968
     */
12969
    public static function getSubscriptionSettings()
12970
    {
12971
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
12972
        if (empty($subscriptionSettings)) {
12973
            // By default allow both settings
12974
            $subscriptionSettings = [
12975
                'allow_add_users_to_lp' => true,
12976
                'allow_add_users_to_lp_category' => true,
12977
            ];
12978
        } else {
12979
            $subscriptionSettings = $subscriptionSettings['options'];
12980
        }
12981
12982
        return $subscriptionSettings;
12983
    }
12984
12985
    /**
12986
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
12987
     */
12988
    public function exportToCourseBuildFormat()
12989
    {
12990
        if (!api_is_allowed_to_edit()) {
12991
            return false;
12992
        }
12993
12994
        $courseBuilder = new CourseBuilder();
12995
        $itemList = [];
12996
        /** @var learnpathItem $item */
12997
        foreach ($this->items as $item) {
12998
            $itemList[$item->get_type()][] = $item->get_path();
12999
        }
13000
13001
        if (empty($itemList)) {
13002
            return false;
13003
        }
13004
13005
        if (isset($itemList['document'])) {
13006
            // Get parents
13007
            foreach ($itemList['document'] as $documentId) {
13008
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
13009
                if (!empty($documentInfo['parents'])) {
13010
                    foreach ($documentInfo['parents'] as $parentInfo) {
13011
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
13012
                            continue;
13013
                        }
13014
                        $itemList['document'][] = $parentInfo['iid'];
13015
                    }
13016
                }
13017
            }
13018
13019
            $courseInfo = api_get_course_info();
13020
            foreach ($itemList['document'] as $documentId) {
13021
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
13022
                $items = DocumentManager::get_resources_from_source_html(
13023
                    $documentInfo['absolute_path'],
13024
                    true,
13025
                    TOOL_DOCUMENT
13026
                );
13027
13028
                if (!empty($items)) {
13029
                    foreach ($items as $item) {
13030
                        // Get information about source url
13031
                        $url = $item[0]; // url
13032
                        $scope = $item[1]; // scope (local, remote)
13033
                        $type = $item[2]; // type (rel, abs, url)
13034
13035
                        $origParseUrl = parse_url($url);
13036
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
13037
13038
                        if ($scope == 'local') {
13039
                            if ($type == 'abs' || $type == 'rel') {
13040
                                $documentFile = strstr($realOrigPath, 'document');
13041
                                if (strpos($realOrigPath, $documentFile) !== false) {
13042
                                    $documentFile = str_replace('document', '', $documentFile);
13043
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
13044
                                    // Document found! Add it to the list
13045
                                    if ($itemDocumentId) {
13046
                                        $itemList['document'][] = $itemDocumentId;
13047
                                    }
13048
                                }
13049
                            }
13050
                        }
13051
                    }
13052
                }
13053
            }
13054
13055
            $courseBuilder->build_documents(
13056
                api_get_session_id(),
13057
                $this->get_course_int_id(),
13058
                true,
13059
                $itemList['document']
13060
            );
13061
        }
13062
13063
        if (isset($itemList['quiz'])) {
13064
            $courseBuilder->build_quizzes(
13065
                api_get_session_id(),
13066
                $this->get_course_int_id(),
13067
                true,
13068
                $itemList['quiz']
13069
            );
13070
        }
13071
13072
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
13073
13074
        /*if (!empty($itemList['thread'])) {
13075
            $postList = [];
13076
            foreach ($itemList['thread'] as $postId) {
13077
                $post = get_post_information($postId);
13078
                if ($post) {
13079
                    if (!isset($itemList['forum'])) {
13080
                        $itemList['forum'] = [];
13081
                    }
13082
                    $itemList['forum'][] = $post['forum_id'];
13083
                    $postList[] = $postId;
13084
                }
13085
            }
13086
13087
            if (!empty($postList)) {
13088
                $courseBuilder->build_forum_posts(
13089
                    $this->get_course_int_id(),
13090
                    null,
13091
                    null,
13092
                    $postList
13093
                );
13094
            }
13095
        }*/
13096
13097
        if (!empty($itemList['thread'])) {
13098
            $threadList = [];
13099
            $em = Database::getManager();
13100
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
13101
            foreach ($itemList['thread'] as $threadId) {
13102
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
13103
                $thread = $repo->find($threadId);
13104
                if ($thread) {
13105
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
13106
                    $threadList[] = $thread->getIid();
13107
                }
13108
            }
13109
13110
            if (!empty($threadList)) {
13111
                $courseBuilder->build_forum_topics(
13112
                    api_get_session_id(),
13113
                    $this->get_course_int_id(),
13114
                    null,
13115
                    $threadList
13116
                );
13117
            }
13118
        }
13119
13120
        $forumCategoryList = [];
13121
        if (isset($itemList['forum'])) {
13122
            foreach ($itemList['forum'] as $forumId) {
13123
                $forumInfo = get_forums($forumId);
13124
                $forumCategoryList[] = $forumInfo['forum_category'];
13125
            }
13126
        }
13127
13128
        if (!empty($forumCategoryList)) {
13129
            $courseBuilder->build_forum_category(
13130
                api_get_session_id(),
13131
                $this->get_course_int_id(),
13132
                true,
13133
                $forumCategoryList
13134
            );
13135
        }
13136
13137
        if (!empty($itemList['forum'])) {
13138
            $courseBuilder->build_forums(
13139
                api_get_session_id(),
13140
                $this->get_course_int_id(),
13141
                true,
13142
                $itemList['forum']
13143
            );
13144
        }
13145
13146
        if (isset($itemList['link'])) {
13147
            $courseBuilder->build_links(
13148
                api_get_session_id(),
13149
                $this->get_course_int_id(),
13150
                true,
13151
                $itemList['link']
13152
            );
13153
        }
13154
13155
        if (!empty($itemList['student_publication'])) {
13156
            $courseBuilder->build_works(
13157
                api_get_session_id(),
13158
                $this->get_course_int_id(),
13159
                true,
13160
                $itemList['student_publication']
13161
            );
13162
        }
13163
13164
        $courseBuilder->build_learnpaths(
13165
            api_get_session_id(),
13166
            $this->get_course_int_id(),
13167
            true,
13168
            [$this->get_id()],
13169
            false
13170
        );
13171
13172
        $courseBuilder->restoreDocumentsFromList();
13173
13174
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
13175
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
13176
        $result = DocumentManager::file_send_for_download(
13177
            $zipPath,
13178
            true,
13179
            $this->get_name().'.zip'
13180
        );
13181
13182
        if ($result) {
13183
            api_not_allowed();
13184
        }
13185
13186
        return true;
13187
    }
13188
13189
    /**
13190
     * Get whether this is a learning path with the accumulated work time or not.
13191
     *
13192
     * @return int
13193
     */
13194
    public function getAccumulateWorkTime()
13195
    {
13196
        return (int) $this->accumulateWorkTime;
13197
    }
13198
13199
    /**
13200
     * Get whether this is a learning path with the accumulated work time or not.
13201
     *
13202
     * @return int
13203
     */
13204
    public function getAccumulateWorkTimeTotalCourse()
13205
    {
13206
        $table = Database::get_course_table(TABLE_LP_MAIN);
13207
        $sql = "SELECT SUM(accumulate_work_time) AS total
13208
                FROM $table
13209
                WHERE c_id = ".$this->course_int_id;
13210
        $result = Database::query($sql);
13211
        $row = Database::fetch_array($result);
13212
13213
        return (int) $row['total'];
13214
    }
13215
13216
    /**
13217
     * Set whether this is a learning path with the accumulated work time or not.
13218
     *
13219
     * @param int $value (0 = false, 1 = true)
13220
     *
13221
     * @return bool
13222
     */
13223
    public function setAccumulateWorkTime($value)
13224
    {
13225
        if (!api_get_configuration_value('lp_minimum_time')) {
13226
            return false;
13227
        }
13228
13229
        $this->accumulateWorkTime = (int) $value;
13230
        $table = Database::get_course_table(TABLE_LP_MAIN);
13231
        $lp_id = $this->get_id();
13232
        $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
13233
                WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
13234
        Database::query($sql);
13235
13236
        return true;
13237
    }
13238
13239
    /**
13240
     * @param int $lpId
13241
     * @param int $courseId
13242
     *
13243
     * @return mixed
13244
     */
13245
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
13246
    {
13247
        $lpId = (int) $lpId;
13248
        $courseId = (int) $courseId;
13249
13250
        $table = Database::get_course_table(TABLE_LP_MAIN);
13251
        $sql = "SELECT accumulate_work_time
13252
                FROM $table
13253
                WHERE c_id = $courseId AND id = $lpId";
13254
        $result = Database::query($sql);
13255
        $row = Database::fetch_array($result);
13256
13257
        return $row['accumulate_work_time'];
13258
    }
13259
13260
    /**
13261
     * @param int $courseId
13262
     *
13263
     * @return int
13264
     */
13265
    public static function getAccumulateWorkTimeTotal($courseId)
13266
    {
13267
        $table = Database::get_course_table(TABLE_LP_MAIN);
13268
        $courseId = (int) $courseId;
13269
        $sql = "SELECT SUM(accumulate_work_time) AS total
13270
                FROM $table
13271
                WHERE c_id = $courseId";
13272
        $result = Database::query($sql);
13273
        $row = Database::fetch_array($result);
13274
13275
        return (int) $row['total'];
13276
    }
13277
13278
    /**
13279
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
13280
     * and put the images in.
13281
     *
13282
     * @return array
13283
     */
13284
    public static function getIconSelect()
13285
    {
13286
        $theme = api_get_visual_theme();
13287
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
13288
        $icons = ['' => get_lang('Please select an option')];
13289
13290
        if (is_dir($path)) {
13291
            $finder = new Finder();
13292
            $finder->files()->in($path);
13293
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
13294
            /** @var SplFileInfo $file */
13295
            foreach ($finder as $file) {
13296
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
13297
                    $icons[$file->getFilename()] = $file->getFilename();
13298
                }
13299
            }
13300
        }
13301
13302
        return $icons;
13303
    }
13304
13305
    /**
13306
     * @param int $lpId
13307
     *
13308
     * @return string
13309
     */
13310
    public static function getSelectedIcon($lpId)
13311
    {
13312
        $extraFieldValue = new ExtraFieldValue('lp');
13313
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
13314
        $icon = '';
13315
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
13316
            $icon = $lpIcon['value'];
13317
        }
13318
13319
        return $icon;
13320
    }
13321
13322
    /**
13323
     * @param int $lpId
13324
     *
13325
     * @return string
13326
     */
13327
    public static function getSelectedIconHtml($lpId)
13328
    {
13329
        $icon = self::getSelectedIcon($lpId);
13330
13331
        if (empty($icon)) {
13332
            return '';
13333
        }
13334
13335
        $theme = api_get_visual_theme();
13336
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
13337
13338
        return Display::img($path);
13339
    }
13340
13341
    /**
13342
     * @param string $value
13343
     *
13344
     * @return string
13345
     */
13346
    public function cleanItemTitle($value)
13347
    {
13348
        $value = Security::remove_XSS(strip_tags($value));
13349
13350
        return $value;
13351
    }
13352
13353
    public function setItemTitle(FormValidator $form)
13354
    {
13355
        if (api_get_configuration_value('save_titles_as_html')) {
13356
            $form->addHtmlEditor(
13357
                'title',
13358
                get_lang('Title'),
13359
                true,
13360
                false,
13361
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
13362
            );
13363
        } else {
13364
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
13365
            $form->applyFilter('title', 'trim');
13366
            $form->applyFilter('title', 'html_filter');
13367
        }
13368
    }
13369
13370
    /**
13371
     * @return array
13372
     */
13373
    public function getItemsForForm($addParentCondition = false)
13374
    {
13375
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
13376
        $course_id = api_get_course_int_id();
13377
13378
        $sql = "SELECT * FROM $tbl_lp_item
13379
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
13380
13381
        if ($addParentCondition) {
13382
            $sql .= ' AND parent_item_id = 0 ';
13383
        }
13384
        $sql .= ' ORDER BY display_order ASC';
13385
13386
        $result = Database::query($sql);
13387
        $arrLP = [];
13388
        while ($row = Database::fetch_array($result)) {
13389
            $arrLP[] = [
13390
                'iid' => $row['iid'],
13391
                'id' => $row['iid'],
13392
                'item_type' => $row['item_type'],
13393
                'title' => $this->cleanItemTitle($row['title']),
13394
                'title_raw' => $row['title'],
13395
                'path' => $row['path'],
13396
                'description' => Security::remove_XSS($row['description']),
13397
                'parent_item_id' => $row['parent_item_id'],
13398
                'previous_item_id' => $row['previous_item_id'],
13399
                'next_item_id' => $row['next_item_id'],
13400
                'display_order' => $row['display_order'],
13401
                'max_score' => $row['max_score'],
13402
                'min_score' => $row['min_score'],
13403
                'mastery_score' => $row['mastery_score'],
13404
                'prerequisite' => $row['prerequisite'],
13405
                'max_time_allowed' => $row['max_time_allowed'],
13406
                'prerequisite_min_score' => $row['prerequisite_min_score'],
13407
                'prerequisite_max_score' => $row['prerequisite_max_score'],
13408
            ];
13409
        }
13410
13411
        return $arrLP;
13412
    }
13413
13414
    /**
13415
     * Get the depth level of LP item.
13416
     *
13417
     * @param array $items
13418
     * @param int   $currentItemId
13419
     *
13420
     * @return int
13421
     */
13422
    private static function get_level_for_item($items, $currentItemId)
13423
    {
13424
        $parentItemId = 0;
13425
        if (isset($items[$currentItemId])) {
13426
            $parentItemId = $items[$currentItemId]->parent;
13427
        }
13428
13429
        if ($parentItemId == 0) {
13430
            return 0;
13431
        } else {
13432
            return self::get_level_for_item($items, $parentItemId) + 1;
13433
        }
13434
    }
13435
13436
    /**
13437
     * Generate the link for a learnpath category as course tool.
13438
     *
13439
     * @param int $categoryId
13440
     *
13441
     * @return string
13442
     */
13443
    private static function getCategoryLinkForTool($categoryId)
13444
    {
13445
        $categoryId = (int) $categoryId;
13446
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
13447
            .http_build_query(
13448
                [
13449
                    'action' => 'view_category',
13450
                    'id' => $categoryId,
13451
                ]
13452
            );
13453
13454
        return $link;
13455
    }
13456
13457
    /**
13458
     * Return the scorm item type object with spaces replaced with _
13459
     * The return result is use to build a css classname like scorm_type_$return.
13460
     *
13461
     * @param $in_type
13462
     *
13463
     * @return mixed
13464
     */
13465
    private static function format_scorm_type_item($in_type)
13466
    {
13467
        return str_replace(' ', '_', $in_type);
13468
    }
13469
13470
    /**
13471
     * Check and obtain the lp final item if exist.
13472
     *
13473
     * @return learnpathItem
13474
     */
13475
    private function getFinalItem()
13476
    {
13477
        if (empty($this->items)) {
13478
            return null;
13479
        }
13480
13481
        foreach ($this->items as $item) {
13482
            if ($item->type !== 'final_item') {
13483
                continue;
13484
            }
13485
13486
            return $item;
13487
        }
13488
    }
13489
13490
    /**
13491
     * Get the LP Final Item Template.
13492
     *
13493
     * @return string
13494
     */
13495
    private function getFinalItemTemplate()
13496
    {
13497
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
13498
    }
13499
13500
    /**
13501
     * Get the LP Final Item Url.
13502
     *
13503
     * @return string
13504
     */
13505
    private function getSavedFinalItem()
13506
    {
13507
        $finalItem = $this->getFinalItem();
13508
        $doc = DocumentManager::get_document_data_by_id(
13509
            $finalItem->path,
13510
            $this->cc
13511
        );
13512
        if ($doc && file_exists($doc['absolute_path'])) {
13513
            return file_get_contents($doc['absolute_path']);
13514
        }
13515
13516
        return '';
13517
    }
13518
}
13519