Completed
Push — master ( 1285bd...aa4c59 )
by Julito
12:22
created

learnpath   F

Complexity

Total Complexity 1806

Size/Duplication

Total Lines 13517
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 7334
dl 0
loc 13517
rs 0.8
c 5
b 0
f 0
wmc 1806

219 Methods

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

How to fix   Complexity   

Complex Class

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

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

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

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\Resource\ResourceLink;
6
use Chamilo\CoreBundle\Framework\Container;
7
use Chamilo\CoreBundle\Repository\CourseRepository;
8
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
9
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
10
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
11
use Chamilo\CourseBundle\Entity\CLink;
12
use Chamilo\CourseBundle\Entity\CLp;
13
use Chamilo\CourseBundle\Entity\CLpCategory;
14
use Chamilo\CourseBundle\Entity\CLpItem;
15
use Chamilo\CourseBundle\Entity\CLpItemView;
16
use Chamilo\CourseBundle\Entity\CQuiz;
17
use Chamilo\CourseBundle\Entity\CShortcut;
18
use Chamilo\CourseBundle\Entity\CTool;
19
use Chamilo\UserBundle\Entity\User;
20
use ChamiloSession as Session;
21
use Gedmo\Sortable\Entity\Repository\SortableRepository;
22
use Symfony\Component\Filesystem\Filesystem;
23
use Symfony\Component\Finder\Finder;
24
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
25
26
/**
27
 * Class learnpath
28
 * This class defines the parent attributes and methods for Chamilo learnpaths
29
 * and SCORM learnpaths. It is used by the scorm class.
30
 *
31
 * @todo decouple class
32
 * *
33
 * @author  Yannick Warnier <[email protected]>
34
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
35
 */
36
class learnpath
37
{
38
    public const MAX_LP_ITEM_TITLE_LENGTH = 32;
39
40
    public $attempt = 0; // The number for the current ID view.
41
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
42
    public $current; // Id of the current item the user is viewing.
43
    public $current_score; // The score of the current item.
44
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
45
    public $current_time_stop; // The time the user closed this resource.
46
    public $default_status = 'not attempted';
47
    public $encoding = 'UTF-8';
48
    public $error = '';
49
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
50
    public $index; // The index of the active learnpath_item in $ordered_items array.
51
    public $items = [];
52
    public $last; // item_id of last item viewed in the learning path.
53
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
54
    public $license; // Which license this course has been given - not used yet on 20060522.
55
    public $lp_id; // DB iid for this learnpath.
56
    public $lp_view_id; // DB ID for lp_view
57
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
58
    public $message = '';
59
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
60
    public $name; // Learnpath name (they generally have one).
61
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
62
    public $path = ''; // Path inside the scorm directory (if scorm).
63
    public $theme; // The current theme of the learning path.
64
    public $preview_image; // The current image of the learning path.
65
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
66
    public $accumulateWorkTime; // The min time of learnpath
67
68
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
69
    public $prevent_reinit = 1;
70
71
    // Describes the mode of progress bar display.
72
    public $seriousgame_mode = 0;
73
    public $progress_bar_mode = '%';
74
75
    // Percentage progress as saved in the db.
76
    public $progress_db = 0;
77
    public $proximity; // Wether the content is distant or local or unknown.
78
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
79
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
80
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
81
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
82
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
83
    public $user_id; //ID of the user that is viewing/using the course
84
    public $update_queue = [];
85
    public $scorm_debug = 0;
86
    public $arrMenu = []; // Array for the menu items.
87
    public $debug = 0; // Logging level.
88
    public $lp_session_id = 0;
89
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
90
    public $prerequisite = 0;
91
    public $use_max_score = 1; // 1 or 0
92
    public $subscribeUsers = 0; // Subscribe users or not
93
    public $created_on = '';
94
    public $modified_on = '';
95
    public $publicated_on = '';
96
    public $expired_on = '';
97
    public $ref = null;
98
    public $course_int_id;
99
    public $course_info = [];
100
    public $categoryId;
101
    public $entity;
102
103
    /**
104
     * Constructor.
105
     * Needs a database handler, a course code and a learnpath id from the database.
106
     * Also builds the list of items into $this->items.
107
     *
108
     * @param string $course  Course code
109
     * @param int    $lp_id   c_lp.iid
110
     * @param int    $user_id
111
     */
112
    public function __construct($course, $lp_id, $user_id)
113
    {
114
        $debug = $this->debug;
115
        $this->encoding = api_get_system_encoding();
116
        if (empty($course)) {
117
            $course = api_get_course_id();
118
        }
119
        $course_info = api_get_course_info($course);
120
        if (!empty($course_info)) {
121
            $this->cc = $course_info['code'];
122
            $this->course_info = $course_info;
123
            $course_id = $course_info['real_id'];
124
        } else {
125
            $this->error = 'Course code does not exist in database.';
126
        }
127
128
        $lp_id = (int) $lp_id;
129
        $course_id = (int) $course_id;
130
        $this->set_course_int_id($course_id);
131
        // Check learnpath ID.
132
        if (empty($lp_id) || empty($course_id)) {
133
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
134
        } else {
135
            $repo = Container::getLpRepository();
136
            /** @var CLp $entity */
137
            $entity = $repo->find($lp_id);
138
            if ($entity) {
0 ignored issues
show
introduced by
$entity is of type Chamilo\CourseBundle\Entity\CLp, thus it always evaluated to true.
Loading history...
139
                $this->entity = $entity;
140
                $this->lp_id = $lp_id;
141
                $this->type = $entity->getLpType();
142
                $this->name = stripslashes($entity->getName());
143
                $this->proximity = $entity->getContentLocal();
144
                $this->theme = $entity->getTheme();
145
                $this->maker = $entity->getContentLocal();
146
                $this->prevent_reinit = $entity->getPreventReinit();
147
                $this->seriousgame_mode = $entity->getSeriousgameMode();
148
                $this->license = $entity->getContentLicense();
149
                $this->scorm_debug = $entity->getDebug();
150
                $this->js_lib = $entity->getJsLib();
151
                $this->path = $entity->getPath();
152
                $this->preview_image = $entity->getPreviewImage();
153
                $this->author = $entity->getAuthor();
154
                $this->hide_toc_frame = $entity->getHideTocFrame();
155
                $this->lp_session_id = $entity->getSessionId();
156
                $this->use_max_score = $entity->getUseMaxScore();
157
                $this->subscribeUsers = $entity->getSubscribeUsers();
158
                $this->created_on = $entity->getCreatedOn()->format('Y-m-d H:i:s');
159
                $this->modified_on = $entity->getModifiedOn()->format('Y-m-d H:i:s');
160
                $this->ref = $entity->getRef();
161
                $this->categoryId = $entity->getCategoryId();
162
                $this->accumulateScormTime = $entity->getAccumulateWorkTime();
163
164
                if (!empty($entity->getPublicatedOn())) {
165
                    $this->publicated_on = $entity->getPublicatedOn()->format('Y-m-d H:i:s');
166
                }
167
168
                if (!empty($entity->getExpiredOn())) {
169
                    $this->expired_on = $entity->getExpiredOn()->format('Y-m-d H:i:s');
170
                }
171
                if ($this->type == 2) {
172
                    if ($entity->getForceCommit() == 1) {
173
                        $this->force_commit = true;
174
                    }
175
                }
176
                $this->mode = $entity->getDefaultViewMod();
177
178
                // Check user ID.
179
                if (empty($user_id)) {
180
                    $this->error = 'User ID is empty';
181
                } else {
182
                    $userInfo = api_get_user_info($user_id);
183
                    if (!empty($userInfo)) {
184
                        $this->user_id = $userInfo['user_id'];
185
                    } else {
186
                        $this->error = 'User ID does not exist in database #'.$user_id;
187
                    }
188
                }
189
190
                // End of variables checking.
191
                $session_id = api_get_session_id();
192
                //  Get the session condition for learning paths of the base + session.
193
                $session = api_get_session_condition($session_id);
194
                // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
195
                $lp_table = Database::get_course_table(TABLE_LP_VIEW);
196
197
                // Selecting by view_count descending allows to get the highest view_count first.
198
                $sql = "SELECT * FROM $lp_table
199
                        WHERE
200
                            c_id = $course_id AND
201
                            lp_id = $lp_id AND
202
                            user_id = $user_id
203
                            $session
204
                        ORDER BY view_count DESC";
205
                $res = Database::query($sql);
206
                if ($debug) {
207
                    error_log('learnpath::__construct() '.__LINE__.' - querying lp_view: '.$sql, 0);
208
                }
209
210
                if (Database::num_rows($res) > 0) {
211
                    if ($debug) {
212
                        error_log('learnpath::__construct() '.__LINE__.' - Found previous view');
213
                    }
214
                    $row = Database::fetch_array($res);
215
                    $this->attempt = $row['view_count'];
216
                    $this->lp_view_id = $row['id'];
217
                    $this->last_item_seen = $row['last_item'];
218
                    $this->progress_db = $row['progress'];
219
                    $this->lp_view_session_id = $row['session_id'];
220
                } elseif (!api_is_invitee()) {
221
                    $this->attempt = 1;
222
                    $params = [
223
                        'c_id' => $course_id,
224
                        'lp_id' => $lp_id,
225
                        'user_id' => $user_id,
226
                        'view_count' => 1,
227
                        'session_id' => $session_id,
228
                        'last_item' => 0,
229
                    ];
230
                    $this->last_item_seen = 0;
231
                    $this->lp_view_session_id = $session_id;
232
                    $this->lp_view_id = Database::insert($lp_table, $params);
233
                    if (!empty($this->lp_view_id)) {
234
                        $sql = "UPDATE $lp_table SET id = iid
235
                                WHERE iid = ".$this->lp_view_id;
236
                        Database::query($sql);
237
                    }
238
                }
239
240
                // Initialise items.
241
                $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
242
                $sql = "SELECT * FROM $lp_item_table
243
                        WHERE c_id = $course_id AND lp_id = '".$this->lp_id."'
244
                        ORDER BY parent_item_id, display_order";
245
                $res = Database::query($sql);
246
247
                $lp_item_id_list = [];
248
                while ($row = Database::fetch_array($res)) {
249
                    $lp_item_id_list[] = $row['iid'];
250
                    switch ($this->type) {
251
                        case 3: //aicc
252
                            $oItem = new aiccItem('db', $row['iid'], $course_id);
253
                            if (is_object($oItem)) {
254
                                $my_item_id = $oItem->get_id();
255
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
256
                                $oItem->set_prevent_reinit($this->prevent_reinit);
257
                                // Don't use reference here as the next loop will make the pointed object change.
258
                                $this->items[$my_item_id] = $oItem;
259
                                $this->refs_list[$oItem->ref] = $my_item_id;
260
                                if ($debug) {
261
                                    error_log(
262
                                        'learnpath::__construct() - '.
263
                                        'aicc object with id '.$my_item_id.
264
                                        ' set in items[]',
265
                                        0
266
                                    );
267
                                }
268
                            }
269
                            break;
270
                        case 2:
271
                            $oItem = new scormItem('db', $row['iid'], $course_id);
272
                            if (is_object($oItem)) {
273
                                $my_item_id = $oItem->get_id();
274
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
275
                                $oItem->set_prevent_reinit($this->prevent_reinit);
276
                                // Don't use reference here as the next loop will make the pointed object change.
277
                                $this->items[$my_item_id] = $oItem;
278
                                $this->refs_list[$oItem->ref] = $my_item_id;
279
                                if ($debug) {
280
                                    error_log('object with id '.$my_item_id.' set in items[]');
281
                                }
282
                            }
283
                            break;
284
                        case 1:
285
                        default:
286
                            if ($debug) {
287
                                error_log('learnpath::__construct() '.__LINE__.' - calling learnpathItem');
288
                            }
289
                            $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
290
291
                            if ($debug) {
292
                                error_log('learnpath::__construct() '.__LINE__.' - end calling learnpathItem');
293
                            }
294
                            if (is_object($oItem)) {
295
                                $my_item_id = $oItem->get_id();
296
                                // Moved down to when we are sure the item_view exists.
297
                                //$oItem->set_lp_view($this->lp_view_id);
298
                                $oItem->set_prevent_reinit($this->prevent_reinit);
299
                                // Don't use reference here as the next loop will make the pointed object change.
300
                                $this->items[$my_item_id] = $oItem;
301
                                $this->refs_list[$my_item_id] = $my_item_id;
302
                                if ($debug) {
303
                                    error_log(
304
                                        'learnpath::__construct() '.__LINE__.
305
                                        ' - object with id '.$my_item_id.' set in items[]'
306
                                    );
307
                                }
308
                            }
309
                            break;
310
                    }
311
312
                    // Setting the object level with variable $this->items[$i][parent]
313
                    foreach ($this->items as $itemLPObject) {
314
                        $level = self::get_level_for_item(
315
                            $this->items,
316
                            $itemLPObject->db_id
317
                        );
318
                        $itemLPObject->level = $level;
319
                    }
320
321
                    // Setting the view in the item object.
322
                    if (is_object($this->items[$row['iid']])) {
323
                        $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
324
                        if ($this->items[$row['iid']]->get_type() == TOOL_HOTPOTATOES) {
325
                            $this->items[$row['iid']]->current_start_time = 0;
326
                            $this->items[$row['iid']]->current_stop_time = 0;
327
                        }
328
                    }
329
                }
330
331
                if (!empty($lp_item_id_list)) {
332
                    $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
333
                    if (!empty($lp_item_id_list_to_string)) {
334
                        // Get last viewing vars.
335
                        $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
336
                        // This query should only return one or zero result.
337
                        $sql = "SELECT lp_item_id, status
338
                                FROM $itemViewTable
339
                                WHERE
340
                                    c_id = $course_id AND
341
                                    lp_view_id = ".$this->get_view_id()." AND
342
                                    lp_item_id IN ('".$lp_item_id_list_to_string."')
343
                                ORDER BY view_count DESC ";
344
                        $status_list = [];
345
                        $res = Database::query($sql);
346
                        while ($row = Database:: fetch_array($res)) {
347
                            $status_list[$row['lp_item_id']] = $row['status'];
348
                        }
349
350
                        foreach ($lp_item_id_list as $item_id) {
351
                            if (isset($status_list[$item_id])) {
352
                                $status = $status_list[$item_id];
353
                                if (is_object($this->items[$item_id])) {
354
                                    $this->items[$item_id]->set_status($status);
355
                                    if (empty($status)) {
356
                                        $this->items[$item_id]->set_status(
357
                                            $this->default_status
358
                                        );
359
                                    }
360
                                }
361
                            } else {
362
                                if (!api_is_invitee()) {
363
                                    if (is_object($this->items[$item_id])) {
364
                                        $this->items[$item_id]->set_status(
365
                                            $this->default_status
366
                                        );
367
                                    }
368
369
                                    if (!empty($this->lp_view_id)) {
370
                                        // Add that row to the lp_item_view table so that
371
                                        // we have something to show in the stats page.
372
                                        $params = [
373
                                            'c_id' => $course_id,
374
                                            'lp_item_id' => $item_id,
375
                                            'lp_view_id' => $this->lp_view_id,
376
                                            'view_count' => 1,
377
                                            'status' => 'not attempted',
378
                                            'start_time' => time(),
379
                                            'total_time' => 0,
380
                                            'score' => 0,
381
                                        ];
382
                                        $insertId = Database::insert($itemViewTable, $params);
383
384
                                        if ($insertId) {
385
                                            $sql = "UPDATE $itemViewTable SET id = iid
386
                                                    WHERE iid = $insertId";
387
                                            Database::query($sql);
388
                                        }
389
390
                                        $this->items[$item_id]->set_lp_view(
391
                                            $this->lp_view_id,
392
                                            $course_id
393
                                        );
394
                                    }
395
                                }
396
                            }
397
                        }
398
                    }
399
                }
400
401
                $this->ordered_items = self::get_flat_ordered_items_list(
402
                    $this->get_id(),
403
                    0,
404
                    $course_id
405
                );
406
                $this->max_ordered_items = 0;
407
                foreach ($this->ordered_items as $index => $dummy) {
408
                    if ($index > $this->max_ordered_items && !empty($dummy)) {
409
                        $this->max_ordered_items = $index;
410
                    }
411
                }
412
                // TODO: Define the current item better.
413
                $this->first();
414
                if ($debug) {
415
                    error_log('lp_view_session_id '.$this->lp_view_session_id);
416
                    error_log('End of learnpath constructor for learnpath '.$this->get_id());
417
                }
418
            } else {
419
                $this->error = 'Learnpath ID does not exist in database ('.$sql.')';
420
            }
421
        }
422
    }
423
424
    public function getEntity(): CLp
425
    {
426
        return $this->entity;
427
    }
428
429
    /**
430
     * @return string
431
     */
432
    public function getCourseCode()
433
    {
434
        return $this->cc;
435
    }
436
437
    /**
438
     * @return int
439
     */
440
    public function get_course_int_id()
441
    {
442
        return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
443
    }
444
445
    /**
446
     * @param $course_id
447
     *
448
     * @return int
449
     */
450
    public function set_course_int_id($course_id)
451
    {
452
        return $this->course_int_id = (int) $course_id;
453
    }
454
455
    /**
456
     * Function rewritten based on old_add_item() from Yannick Warnier.
457
     * Due the fact that users can decide where the item should come, I had to overlook this function and
458
     * I found it better to rewrite it. Old function is still available.
459
     * Added also the possibility to add a description.
460
     *
461
     * @param int    $parent
462
     * @param int    $previous
463
     * @param string $type
464
     * @param int    $id               resource ID (ref)
465
     * @param string $title
466
     * @param string $description
467
     * @param int    $prerequisites
468
     * @param int    $max_time_allowed
469
     * @param int    $userId
470
     *
471
     * @return int
472
     */
473
    public function add_item(
474
        $parent,
475
        $previous,
476
        $type = 'dir',
477
        $id,
478
        $title,
479
        $description,
480
        $prerequisites = 0,
481
        $max_time_allowed = 0,
482
        $userId = 0
483
    ) {
484
        $course_id = $this->course_info['real_id'];
485
        if (empty($course_id)) {
486
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
487
            $this->course_info = api_get_course_info($this->cc);
488
            $course_id = $this->course_info['real_id'];
489
        }
490
        $userId = empty($userId) ? api_get_user_id() : $userId;
491
        $sessionId = api_get_session_id();
492
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
493
        $_course = $this->course_info;
494
        $parent = (int) $parent;
495
        $previous = (int) $previous;
496
        $id = (int) $id;
497
        $max_time_allowed = htmlentities($max_time_allowed);
498
        if (empty($max_time_allowed)) {
499
            $max_time_allowed = 0;
500
        }
501
        $sql = "SELECT COUNT(iid) AS num
502
                FROM $tbl_lp_item
503
                WHERE
504
                    c_id = $course_id AND
505
                    lp_id = ".$this->get_id()." AND
506
                    parent_item_id = $parent ";
507
508
        $res_count = Database::query($sql);
509
        $row = Database::fetch_array($res_count);
510
        $num = $row['num'];
511
512
        $tmp_previous = 0;
513
        $display_order = 0;
514
        $next = 0;
515
        if ($num > 0) {
516
            if (empty($previous)) {
517
                $sql = "SELECT iid, next_item_id, display_order
518
                        FROM $tbl_lp_item
519
                        WHERE
520
                            c_id = $course_id AND
521
                            lp_id = ".$this->get_id()." AND
522
                            parent_item_id = $parent AND
523
                            previous_item_id = 0 OR
524
                            previous_item_id = $parent";
525
                $result = Database::query($sql);
526
                $row = Database::fetch_array($result);
527
                if ($row) {
528
                    $next = $row['iid'];
529
                }
530
            } else {
531
                $previous = (int) $previous;
532
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
533
						FROM $tbl_lp_item
534
                        WHERE
535
                            c_id = $course_id AND
536
                            lp_id = ".$this->get_id()." AND
537
                            id = $previous";
538
                $result = Database::query($sql);
539
                $row = Database::fetch_array($result);
540
                if ($row) {
541
                    $tmp_previous = $row['iid'];
542
                    $next = $row['next_item_id'];
543
                    $display_order = $row['display_order'];
544
                }
545
            }
546
        }
547
548
        $id = (int) $id;
549
        $typeCleaned = Database::escape_string($type);
550
        $max_score = 100;
551
        if ($type === 'quiz') {
552
            $sql = 'SELECT SUM(ponderation)
553
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
554
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
555
                    ON
556
                        quiz_question.id = quiz_rel_question.question_id AND
557
                        quiz_question.c_id = quiz_rel_question.c_id
558
                    WHERE
559
                        quiz_rel_question.exercice_id = '.$id." AND
560
                        quiz_question.c_id = $course_id AND
561
                        quiz_rel_question.c_id = $course_id ";
562
            $rsQuiz = Database::query($sql);
563
            $max_score = Database::result($rsQuiz, 0, 0);
564
565
            // Disabling the exercise if we add it inside a LP
566
            $exercise = new Exercise($course_id);
567
            $exercise->read($id);
568
            $exercise->disable();
569
            $exercise->save();
570
        }
571
572
        $params = [
573
            'c_id' => $course_id,
574
            'lp_id' => $this->get_id(),
575
            'item_type' => $typeCleaned,
576
            'ref' => '',
577
            'title' => $title,
578
            'description' => $description,
579
            'path' => $id,
580
            'max_score' => $max_score,
581
            'parent_item_id' => $parent,
582
            'previous_item_id' => $previous,
583
            'next_item_id' => (int) $next,
584
            'display_order' => $display_order + 1,
585
            'prerequisite' => $prerequisites,
586
            'max_time_allowed' => $max_time_allowed,
587
            'min_score' => 0,
588
            'launch_data' => '',
589
        ];
590
591
        if ($prerequisites != 0) {
592
            $params['prerequisite'] = $prerequisites;
593
        }
594
595
        $new_item_id = Database::insert($tbl_lp_item, $params);
596
        if ($new_item_id) {
597
            $sql = "UPDATE $tbl_lp_item SET id = iid WHERE iid = $new_item_id";
598
            Database::query($sql);
599
600
            if (!empty($next)) {
601
                $sql = "UPDATE $tbl_lp_item
602
                        SET previous_item_id = $new_item_id
603
                        WHERE c_id = $course_id AND id = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
604
                Database::query($sql);
605
            }
606
607
            // Update the item that should be before the new item.
608
            if (!empty($tmp_previous)) {
609
                $sql = "UPDATE $tbl_lp_item
610
                        SET next_item_id = $new_item_id
611
                        WHERE c_id = $course_id AND id = $tmp_previous";
612
                Database::query($sql);
613
            }
614
615
            // Update all the items after the new item.
616
            $sql = "UPDATE $tbl_lp_item
617
                        SET display_order = display_order + 1
618
                    WHERE
619
                        c_id = $course_id AND
620
                        lp_id = ".$this->get_id()." AND
621
                        iid <> $new_item_id AND
622
                        parent_item_id = $parent AND
623
                        display_order > $display_order";
624
            Database::query($sql);
625
626
            // Update the item that should come after the new item.
627
            $sql = "UPDATE $tbl_lp_item
628
                    SET ref = $new_item_id
629
                    WHERE c_id = $course_id AND iid = $new_item_id";
630
            Database::query($sql);
631
632
            $sql = "UPDATE $tbl_lp_item
633
                    SET previous_item_id = ".$this->getLastInFirstLevel()."
634
                    WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
635
            Database::query($sql);
636
637
            // Upload audio.
638
            if (!empty($_FILES['mp3']['name'])) {
639
                // Create the audio folder if it does not exist yet.
640
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
641
                if (!is_dir($filepath.'audio')) {
642
                    mkdir(
643
                        $filepath.'audio',
644
                        api_get_permissions_for_new_directories()
645
                    );
646
                    $audio_id = DocumentManager::addDocument(
647
                        $_course,
648
                        '/audio',
649
                        'folder',
650
                        0,
651
                        'audio',
652
                        '',
653
                        0,
654
                        true,
655
                        null,
656
                        $sessionId,
657
                        $userId
658
                    );
659
                }
660
661
                $file_path = handle_uploaded_document(
662
                    $_course,
663
                    $_FILES['mp3'],
664
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
665
                    '/audio',
666
                    $userId,
667
                    '',
668
                    '',
669
                    '',
670
                    '',
671
                    false
672
                );
673
674
                // Getting the filename only.
675
                $file_components = explode('/', $file_path);
676
                $file = $file_components[count($file_components) - 1];
677
678
                // Store the mp3 file in the lp_item table.
679
                $sql = "UPDATE $tbl_lp_item SET
680
                          audio = '".Database::escape_string($file)."'
681
                        WHERE iid = '".intval($new_item_id)."'";
682
                Database::query($sql);
683
            }
684
        }
685
686
        return $new_item_id;
687
    }
688
689
    /**
690
     * Static admin function allowing addition of a learnpath to a course.
691
     *
692
     * @param string $courseCode
693
     * @param string $name
694
     * @param string $description
695
     * @param string $learnpath
696
     * @param string $origin
697
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
698
     * @param string $publicated_on
699
     * @param string $expired_on
700
     * @param int    $categoryId
701
     * @param int    $userId
702
     *
703
     * @return int The new learnpath ID on success, 0 on failure
704
     */
705
    public static function add_lp(
706
        $courseCode,
707
        $name,
708
        $description = '',
709
        $learnpath = 'guess',
710
        $origin = 'zip',
711
        $zipname = '',
712
        $publicated_on = '',
713
        $expired_on = '',
714
        $categoryId = 0,
715
        $userId = 0
716
    ) {
717
        global $charset;
718
719
        if (!empty($courseCode)) {
720
            $courseInfo = api_get_course_info($courseCode);
721
            $course_id = $courseInfo['real_id'];
722
        } else {
723
            $course_id = api_get_course_int_id();
724
            $courseInfo = api_get_course_info();
725
        }
726
727
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
728
        // Check course code exists.
729
        // Check lp_name doesn't exist, otherwise append something.
730
        $i = 0;
731
        $categoryId = (int) $categoryId;
732
        // Session id.
733
        $session_id = api_get_session_id();
734
        $userId = empty($userId) ? api_get_user_id() : $userId;
735
736
        if (empty($publicated_on)) {
737
            $publicated_on = null;
738
        } else {
739
            $publicated_on = api_get_utc_datetime($publicated_on, true, true);
740
        }
741
742
        if (empty($expired_on)) {
743
            $expired_on = null;
744
        } else {
745
            $expired_on = api_get_utc_datetime($expired_on, true, true);
746
        }
747
748
        $check_name = "SELECT * FROM $tbl_lp
749
                       WHERE c_id = $course_id AND name = '".Database::escape_string($name)."'";
750
        $res_name = Database::query($check_name);
751
752
        while (Database::num_rows($res_name)) {
753
            // There is already one such name, update the current one a bit.
754
            $i++;
755
            $name = $name.' - '.$i;
756
            $check_name = "SELECT * FROM $tbl_lp
757
                           WHERE c_id = $course_id AND name = '".Database::escape_string($name)."' ";
758
            $res_name = Database::query($check_name);
759
        }
760
        // New name does not exist yet; keep it.
761
        // Escape description.
762
        // Kevin: added htmlentities().
763
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
764
        $type = 1;
765
        switch ($learnpath) {
766
            case 'guess':
767
            case 'aicc':
768
                break;
769
            case 'dokeos':
770
            case 'chamilo':
771
                $type = 1;
772
                break;
773
        }
774
775
        $id = null;
776
        switch ($origin) {
777
            case 'zip':
778
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
779
                break;
780
            case 'manual':
781
            default:
782
                $get_max = "SELECT MAX(display_order)
783
                            FROM $tbl_lp WHERE c_id = $course_id";
784
                $res_max = Database::query($get_max);
785
                if (Database::num_rows($res_max) < 1) {
786
                    $dsp = 1;
787
                } else {
788
                    $row = Database::fetch_array($res_max);
789
                    $dsp = $row[0] + 1;
790
                }
791
792
                $lp = new CLp();
793
                $lp
794
                    ->setCId($course_id)
795
                    ->setLpType($type)
796
                    ->setName($name)
797
                    ->setDescription($description)
798
                    ->setDisplayOrder($dsp)
799
                    ->setSessionId($session_id)
800
                    ->setCategoryId($categoryId)
801
                    ->setPublicatedOn($publicated_on)
802
                    ->setExpiredOn($expired_on)
803
                ;
804
805
                $repo = Container::getLpRepository();
806
                $em = $repo->getEntityManager();
807
                $em->persist($lp);
808
                $courseEntity = api_get_course_entity($courseInfo['real_id']);
809
810
                $repo->addResourceToCourse(
811
                    $lp,
812
                    ResourceLink::VISIBILITY_PUBLISHED,
813
                    api_get_user_entity(api_get_user_id()),
814
                    $courseEntity,
815
                    api_get_session_entity(),
816
                    api_get_group_entity()
817
                );
818
819
                $em->flush();
820
                if ($lp->getIid()) {
821
                    $id = $lp->getIid();
822
                    $sql = "UPDATE $tbl_lp SET id = iid WHERE iid = $id";
823
                    Database::query($sql);
824
                }
825
826
                // Insert into item_property.
827
                /*api_item_property_update(
828
                    $courseInfo,
829
                    TOOL_LEARNPATH,
830
                    $id,
831
                    'LearnpathAdded',
832
                    $userId
833
                );
834
                api_set_default_visibility(
835
                    $id,
836
                    TOOL_LEARNPATH,
837
                    0,
838
                    $courseInfo,
839
                    $session_id,
840
                    $userId
841
                );*/
842
843
                break;
844
        }
845
846
        return $id;
847
    }
848
849
    /**
850
     * Auto completes the parents of an item in case it's been completed or passed.
851
     *
852
     * @param int $item Optional ID of the item from which to look for parents
853
     */
854
    public function autocomplete_parents($item)
855
    {
856
        $debug = $this->debug;
857
858
        if (empty($item)) {
859
            $item = $this->current;
860
        }
861
862
        $currentItem = $this->getItem($item);
863
        if ($currentItem) {
864
            $parent_id = $currentItem->get_parent();
865
            $parent = $this->getItem($parent_id);
866
            if ($parent) {
867
                // if $item points to an object and there is a parent.
868
                if ($debug) {
869
                    error_log(
870
                        'Autocompleting parent of item '.$item.' '.
871
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
872
                        0
873
                    );
874
                }
875
876
                // New experiment including failed and browsed in completed status.
877
                //$current_status = $currentItem->get_status();
878
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
879
                // Fixes chapter auto complete
880
                if (true) {
881
                    // If the current item is completed or passes or succeeded.
882
                    $updateParentStatus = true;
883
                    if ($debug) {
884
                        error_log('Status of current item is alright');
885
                    }
886
887
                    foreach ($parent->get_children() as $childItemId) {
888
                        $childItem = $this->getItem($childItemId);
889
890
                        // If children was not set try to get the info
891
                        if (empty($childItem->db_item_view_id)) {
892
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
893
                        }
894
895
                        // Check all his brothers (parent's children) for completion status.
896
                        if ($childItemId != $item) {
897
                            if ($debug) {
898
                                error_log(
899
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
900
                                    0
901
                                );
902
                            }
903
                            // Trying completing parents of failed and browsed items as well.
904
                            if ($childItem->status_is(
905
                                [
906
                                    'completed',
907
                                    'passed',
908
                                    'succeeded',
909
                                    'browsed',
910
                                    'failed',
911
                                ]
912
                            )
913
                            ) {
914
                                // Keep completion status to true.
915
                                continue;
916
                            } else {
917
                                if ($debug > 2) {
918
                                    error_log(
919
                                        '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,
920
                                        0
921
                                    );
922
                                }
923
                                $updateParentStatus = false;
924
                                break;
925
                            }
926
                        }
927
                    }
928
929
                    if ($updateParentStatus) {
930
                        // If all the children were completed:
931
                        $parent->set_status('completed');
932
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
933
                        // Force the status to "completed"
934
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
935
                        $this->update_queue[$parent->get_id()] = 'completed';
936
                        if ($debug) {
937
                            error_log(
938
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
939
                                print_r($this->update_queue, 1),
940
                                0
941
                            );
942
                        }
943
                        // Recursive call.
944
                        $this->autocomplete_parents($parent->get_id());
945
                    }
946
                }
947
            } else {
948
                if ($debug) {
949
                    error_log("Parent #$parent_id does not exists");
950
                }
951
            }
952
        } else {
953
            if ($debug) {
954
                error_log("#$item is an item that doesn't have parents");
955
            }
956
        }
957
    }
958
959
    /**
960
     * Closes the current resource.
961
     *
962
     * Stops the timer
963
     * Saves into the database if required
964
     * Clears the current resource data from this object
965
     *
966
     * @return bool True on success, false on failure
967
     */
968
    public function close()
969
    {
970
        if (empty($this->lp_id)) {
971
            $this->error = 'Trying to close this learnpath but no ID is set';
972
973
            return false;
974
        }
975
        $this->current_time_stop = time();
976
        $this->ordered_items = [];
977
        $this->index = 0;
978
        unset($this->lp_id);
979
        //unset other stuff
980
        return true;
981
    }
982
983
    /**
984
     * Static admin function allowing removal of a learnpath.
985
     *
986
     * @param array  $courseInfo
987
     * @param int    $id         Learnpath ID
988
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
989
     *
990
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
991
     */
992
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
993
    {
994
        $course_id = api_get_course_int_id();
995
        if (!empty($courseInfo)) {
996
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
997
        }
998
999
        // TODO: Implement a way of getting this to work when the current object is not set.
1000
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
1001
        // If an ID is specifically given and the current LP is not the same, prevent delete.
1002
        if (!empty($id) && ($id != $this->lp_id)) {
1003
            return false;
1004
        }
1005
1006
        $lp = Database::get_course_table(TABLE_LP_MAIN);
1007
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1008
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
1009
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1010
1011
        // Delete lp item id.
1012
        foreach ($this->items as $lpItemId => $dummy) {
1013
            $sql = "DELETE FROM $lp_item_view
1014
                    WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
1015
            Database::query($sql);
1016
        }
1017
1018
        // Proposed by Christophe (nickname: clefevre)
1019
        $sql = "DELETE FROM $lp_item
1020
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1021
        Database::query($sql);
1022
1023
        $sql = "DELETE FROM $lp_view
1024
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1025
        Database::query($sql);
1026
1027
        //self::toggle_publish($this->lp_id, 'i');
1028
1029
        if ($this->type == 2 || $this->type == 3) {
1030
            // This is a scorm learning path, delete the files as well.
1031
            $sql = "SELECT path FROM $lp
1032
                    WHERE iid = ".$this->lp_id;
1033
            $res = Database::query($sql);
1034
            if (Database::num_rows($res) > 0) {
1035
                $row = Database::fetch_array($res);
1036
                $path = $row['path'];
1037
                $sql = "SELECT id FROM $lp
1038
                        WHERE
1039
                            c_id = $course_id AND
1040
                            path = '$path' AND
1041
                            iid != ".$this->lp_id;
1042
                $res = Database::query($sql);
1043
                if (Database::num_rows($res) > 0) {
1044
                    // Another learning path uses this directory, so don't delete it.
1045
                    if ($this->debug > 2) {
1046
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1047
                    }
1048
                } else {
1049
                    // No other LP uses that directory, delete it.
1050
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1051
                    // The absolute system path for this course.
1052
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
1053
                    if ($delete == 'remove' && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1054
                        if ($this->debug > 2) {
1055
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1056
                        }
1057
                        // Proposed by Christophe (clefevre).
1058
                        if (strcmp(substr($path, -2), "/.") == 0) {
1059
                            $path = substr($path, 0, -1); // Remove "." at the end.
1060
                        }
1061
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1062
                        rmdirr($course_scorm_dir.$path);
1063
                    }
1064
                }
1065
            }
1066
        }
1067
1068
        /*$tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1069
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1070
        // Delete tools
1071
        $sql = "DELETE FROM $tbl_tool
1072
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1073
        Database::query($sql);*/
1074
1075
        /*$sql = "DELETE FROM $lp
1076
                WHERE iid = ".$this->lp_id;
1077
        Database::query($sql);*/
1078
        $repo = Container::getLpRepository();
1079
        $lp = $repo->find($this->lp_id);
1080
        $repo->getEntityManager()->remove($lp);
1081
        $repo->getEntityManager()->flush();
1082
1083
        // Updates the display order of all lps.
1084
        $this->update_display_order();
1085
1086
        /*api_item_property_update(
1087
            api_get_course_info(),
1088
            TOOL_LEARNPATH,
1089
            $this->lp_id,
1090
            'delete',
1091
            api_get_user_id()
1092
        );*/
1093
1094
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1095
            api_get_course_id(),
1096
            4,
1097
            $id,
1098
            api_get_session_id()
1099
        );
1100
1101
        if ($link_info !== false) {
1102
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1103
        }
1104
1105
        if (api_get_setting('search_enabled') == 'true') {
1106
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1107
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1108
        }
1109
    }
1110
1111
    /**
1112
     * Removes all the children of one item - dangerous!
1113
     *
1114
     * @param int $id Element ID of which children have to be removed
1115
     *
1116
     * @return int Total number of children removed
1117
     */
1118
    public function delete_children_items($id)
1119
    {
1120
        $course_id = $this->course_info['real_id'];
1121
1122
        $num = 0;
1123
        $id = (int) $id;
1124
        if (empty($id) || empty($course_id)) {
1125
            return false;
1126
        }
1127
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1128
        $sql = "SELECT * FROM $lp_item
1129
                WHERE c_id = $course_id AND parent_item_id = $id";
1130
        $res = Database::query($sql);
1131
        while ($row = Database::fetch_array($res)) {
1132
            $num += $this->delete_children_items($row['iid']);
1133
            $sql = "DELETE FROM $lp_item
1134
                    WHERE c_id = $course_id AND iid = ".$row['iid'];
1135
            Database::query($sql);
1136
            $num++;
1137
        }
1138
1139
        return $num;
1140
    }
1141
1142
    /**
1143
     * Removes an item from the current learnpath.
1144
     *
1145
     * @param int $id Elem ID (0 if first)
1146
     *
1147
     * @return int Number of elements moved
1148
     *
1149
     * @todo implement resource removal
1150
     */
1151
    public function delete_item($id)
1152
    {
1153
        $course_id = api_get_course_int_id();
1154
        $id = (int) $id;
1155
        // TODO: Implement the resource removal.
1156
        if (empty($id) || empty($course_id)) {
1157
            return false;
1158
        }
1159
        // First select item to get previous, next, and display order.
1160
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1161
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1162
        $res_sel = Database::query($sql_sel);
1163
        if (Database::num_rows($res_sel) < 1) {
1164
            return false;
1165
        }
1166
        $row = Database::fetch_array($res_sel);
1167
        $previous = $row['previous_item_id'];
1168
        $next = $row['next_item_id'];
1169
        $display = $row['display_order'];
1170
        $parent = $row['parent_item_id'];
1171
        $lp = $row['lp_id'];
1172
        // Delete children items.
1173
        $this->delete_children_items($id);
1174
        // Now delete the item.
1175
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1176
        Database::query($sql_del);
1177
        // Now update surrounding items.
1178
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1179
                    WHERE iid = $previous";
1180
        Database::query($sql_upd);
1181
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1182
                    WHERE iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
1183
        Database::query($sql_upd);
1184
        // Now update all following items with new display order.
1185
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1186
                    WHERE
1187
                        c_id = $course_id AND
1188
                        lp_id = $lp AND
1189
                        parent_item_id = $parent AND
1190
                        display_order > $display";
1191
        Database::query($sql_all);
1192
1193
        //Removing prerequisites since the item will not longer exist
1194
        $sql_all = "UPDATE $lp_item SET prerequisite = ''
1195
                    WHERE c_id = $course_id AND prerequisite = $id";
1196
        Database::query($sql_all);
1197
1198
        $sql = "UPDATE $lp_item
1199
                SET previous_item_id = ".$this->getLastInFirstLevel()."
1200
                WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
1201
        Database::query($sql);
1202
1203
        // Remove from search engine if enabled.
1204
        if (api_get_setting('search_enabled') === 'true') {
1205
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1206
            $sql = 'SELECT * FROM %s
1207
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1208
                    LIMIT 1';
1209
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1210
            $res = Database::query($sql);
1211
            if (Database::num_rows($res) > 0) {
1212
                $row2 = Database::fetch_array($res);
1213
                $di = new ChamiloIndexer();
1214
                $di->remove_document($row2['search_did']);
1215
            }
1216
            $sql = 'DELETE FROM %s
1217
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1218
                    LIMIT 1';
1219
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1220
            Database::query($sql);
1221
        }
1222
    }
1223
1224
    /**
1225
     * Updates an item's content in place.
1226
     *
1227
     * @param int    $id               Element ID
1228
     * @param int    $parent           Parent item ID
1229
     * @param int    $previous         Previous item ID
1230
     * @param string $title            Item title
1231
     * @param string $description      Item description
1232
     * @param string $prerequisites    Prerequisites (optional)
1233
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1234
     * @param int    $max_time_allowed
1235
     * @param string $url
1236
     *
1237
     * @return bool True on success, false on error
1238
     */
1239
    public function edit_item(
1240
        $id,
1241
        $parent,
1242
        $previous,
1243
        $title,
1244
        $description,
1245
        $prerequisites = '0',
1246
        $audio = [],
1247
        $max_time_allowed = 0,
1248
        $url = ''
1249
    ) {
1250
        $course_id = api_get_course_int_id();
1251
        $_course = api_get_course_info();
1252
        $id = (int) $id;
1253
1254
        if (empty($max_time_allowed)) {
1255
            $max_time_allowed = 0;
1256
        }
1257
1258
        if (empty($id) || empty($_course)) {
1259
            return false;
1260
        }
1261
1262
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1263
        $sql = "SELECT * FROM $tbl_lp_item
1264
                WHERE iid = $id";
1265
        $res_select = Database::query($sql);
1266
        $row_select = Database::fetch_array($res_select);
1267
        $audio_update_sql = '';
1268
        if (is_array($audio) && !empty($audio['tmp_name']) && $audio['error'] === 0) {
1269
            // Create the audio folder if it does not exist yet.
1270
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1271
            if (!is_dir($filepath.'audio')) {
1272
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1273
                $audio_id = DocumentManager::addDocument(
1274
                    $_course,
1275
                    '/audio',
1276
                    'folder',
1277
                    0,
1278
                    'audio'
1279
                );
1280
            }
1281
1282
            // Upload file in documents.
1283
            $pi = pathinfo($audio['name']);
1284
            if ($pi['extension'] === 'mp3') {
1285
                $c_det = api_get_course_info($this->cc);
1286
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1287
                $path = handle_uploaded_document(
1288
                    $c_det,
1289
                    $audio,
1290
                    $bp,
1291
                    '/audio',
1292
                    api_get_user_id(),
1293
                    0,
1294
                    null,
1295
                    0,
1296
                    'rename',
1297
                    false,
1298
                    0
1299
                );
1300
                $path = substr($path, 7);
1301
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1302
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1303
            }
1304
        }
1305
1306
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1307
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1308
1309
        // TODO: htmlspecialchars to be checked for encoding related problems.
1310
        if ($same_parent && $same_previous) {
1311
            // Only update title and description.
1312
            $sql = "UPDATE $tbl_lp_item
1313
                    SET title = '".Database::escape_string($title)."',
1314
                        prerequisite = '".$prerequisites."',
1315
                        description = '".Database::escape_string($description)."'
1316
                        ".$audio_update_sql.",
1317
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1318
                    WHERE iid = $id";
1319
            Database::query($sql);
1320
        } else {
1321
            $old_parent = $row_select['parent_item_id'];
1322
            $old_previous = $row_select['previous_item_id'];
1323
            $old_next = $row_select['next_item_id'];
1324
            $old_order = $row_select['display_order'];
1325
            $old_prerequisite = $row_select['prerequisite'];
1326
            $old_max_time_allowed = $row_select['max_time_allowed'];
1327
1328
            /* BEGIN -- virtually remove the current item id */
1329
            /* for the next and previous item it is like the current item doesn't exist anymore */
1330
            if ($old_previous != 0) {
1331
                // Next
1332
                $sql = "UPDATE $tbl_lp_item
1333
                        SET next_item_id = $old_next
1334
                        WHERE iid = $old_previous";
1335
                Database::query($sql);
1336
            }
1337
1338
            if (!empty($old_next)) {
1339
                // Previous
1340
                $sql = "UPDATE $tbl_lp_item
1341
                        SET previous_item_id = $old_previous
1342
                        WHERE iid = $old_next";
1343
                Database::query($sql);
1344
            }
1345
1346
            // display_order - 1 for every item with a display_order
1347
            // bigger then the display_order of the current item.
1348
            $sql = "UPDATE $tbl_lp_item
1349
                    SET display_order = display_order - 1
1350
                    WHERE
1351
                        c_id = $course_id AND
1352
                        display_order > $old_order AND
1353
                        lp_id = ".$this->lp_id." AND
1354
                        parent_item_id = $old_parent";
1355
            Database::query($sql);
1356
            /* END -- virtually remove the current item id */
1357
1358
            /* BEGIN -- update the current item id to his new location */
1359
            if ($previous == 0) {
1360
                // Select the data of the item that should come after the current item.
1361
                $sql = "SELECT id, display_order
1362
                        FROM $tbl_lp_item
1363
                        WHERE
1364
                            c_id = $course_id AND
1365
                            lp_id = ".$this->lp_id." AND
1366
                            parent_item_id = $parent AND
1367
                            previous_item_id = $previous";
1368
                $res_select_old = Database::query($sql);
1369
                $row_select_old = Database::fetch_array($res_select_old);
1370
1371
                // If the new parent didn't have children before.
1372
                if (Database::num_rows($res_select_old) == 0) {
1373
                    $new_next = 0;
1374
                    $new_order = 1;
1375
                } else {
1376
                    $new_next = $row_select_old['id'];
1377
                    $new_order = $row_select_old['display_order'];
1378
                }
1379
            } else {
1380
                // Select the data of the item that should come before the current item.
1381
                $sql = "SELECT next_item_id, display_order
1382
                        FROM $tbl_lp_item
1383
                        WHERE iid = $previous";
1384
                $res_select_old = Database::query($sql);
1385
                $row_select_old = Database::fetch_array($res_select_old);
1386
                $new_next = $row_select_old['next_item_id'];
1387
                $new_order = $row_select_old['display_order'] + 1;
1388
            }
1389
1390
            // TODO: htmlspecialchars to be checked for encoding related problems.
1391
            // Update the current item with the new data.
1392
            $sql = "UPDATE $tbl_lp_item
1393
                    SET
1394
                        title = '".Database::escape_string($title)."',
1395
                        description = '".Database::escape_string($description)."',
1396
                        parent_item_id = $parent,
1397
                        previous_item_id = $previous,
1398
                        next_item_id = $new_next,
1399
                        display_order = $new_order
1400
                        $audio_update_sql
1401
                    WHERE iid = $id";
1402
            Database::query($sql);
1403
1404
            if ($previous != 0) {
1405
                // Update the previous item's next_item_id.
1406
                $sql = "UPDATE $tbl_lp_item
1407
                        SET next_item_id = $id
1408
                        WHERE iid = $previous";
1409
                Database::query($sql);
1410
            }
1411
1412
            if (!empty($new_next)) {
1413
                // Update the next item's previous_item_id.
1414
                $sql = "UPDATE $tbl_lp_item
1415
                        SET previous_item_id = $id
1416
                        WHERE iid = $new_next";
1417
                Database::query($sql);
1418
            }
1419
1420
            if ($old_prerequisite != $prerequisites) {
1421
                $sql = "UPDATE $tbl_lp_item
1422
                        SET prerequisite = '$prerequisites'
1423
                        WHERE iid = $id";
1424
                Database::query($sql);
1425
            }
1426
1427
            if ($old_max_time_allowed != $max_time_allowed) {
1428
                // update max time allowed
1429
                $sql = "UPDATE $tbl_lp_item
1430
                        SET max_time_allowed = $max_time_allowed
1431
                        WHERE iid = $id";
1432
                Database::query($sql);
1433
            }
1434
1435
            // Update all the items with the same or a bigger display_order than the current item.
1436
            $sql = "UPDATE $tbl_lp_item
1437
                    SET display_order = display_order + 1
1438
                    WHERE
1439
                       c_id = $course_id AND
1440
                       lp_id = ".$this->get_id()." AND
1441
                       iid <> $id AND
1442
                       parent_item_id = $parent AND
1443
                       display_order >= $new_order";
1444
            Database::query($sql);
1445
        }
1446
1447
        if ($row_select['item_type'] == 'link') {
1448
            $link = new Link();
1449
            $linkId = $row_select['path'];
1450
            $link->updateLink($linkId, $url);
1451
        }
1452
    }
1453
1454
    /**
1455
     * Updates an item's prereq in place.
1456
     *
1457
     * @param int    $id              Element ID
1458
     * @param string $prerequisite_id Prerequisite Element ID
1459
     * @param int    $minScore        Prerequisite min score
1460
     * @param int    $maxScore        Prerequisite max score
1461
     *
1462
     * @return bool True on success, false on error
1463
     */
1464
    public function edit_item_prereq(
1465
        $id,
1466
        $prerequisite_id,
1467
        $minScore = 0,
1468
        $maxScore = 100
1469
    ) {
1470
        $id = (int) $id;
1471
        $prerequisite_id = (int) $prerequisite_id;
1472
1473
        if (empty($id)) {
1474
            return false;
1475
        }
1476
1477
        if (empty($minScore) || $minScore < 0) {
1478
            $minScore = 0;
1479
        }
1480
1481
        if (empty($maxScore) || $maxScore < 0) {
1482
            $maxScore = 100;
1483
        }
1484
1485
        $minScore = floatval($minScore);
1486
        $maxScore = floatval($maxScore);
1487
1488
        if (empty($prerequisite_id)) {
1489
            $prerequisite_id = 'NULL';
1490
            $minScore = 0;
1491
            $maxScore = 100;
1492
        }
1493
1494
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1495
        $sql = " UPDATE $tbl_lp_item
1496
                 SET
1497
                    prerequisite = $prerequisite_id ,
1498
                    prerequisite_min_score = $minScore ,
1499
                    prerequisite_max_score = $maxScore
1500
                 WHERE iid = $id";
1501
1502
        Database::query($sql);
1503
1504
        return true;
1505
    }
1506
1507
    /**
1508
     * Get the specific prefix index terms of this learning path.
1509
     *
1510
     * @param string $prefix
1511
     *
1512
     * @return array Array of terms
1513
     */
1514
    public function get_common_index_terms_by_prefix($prefix)
1515
    {
1516
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1517
        $terms = get_specific_field_values_list_by_prefix(
1518
            $prefix,
1519
            $this->cc,
1520
            TOOL_LEARNPATH,
1521
            $this->lp_id
1522
        );
1523
        $prefix_terms = [];
1524
        if (!empty($terms)) {
1525
            foreach ($terms as $term) {
1526
                $prefix_terms[] = $term['value'];
1527
            }
1528
        }
1529
1530
        return $prefix_terms;
1531
    }
1532
1533
    /**
1534
     * Gets the number of items currently completed.
1535
     *
1536
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1537
     *
1538
     * @return int The number of items currently completed
1539
     */
1540
    public function get_complete_items_count($failedStatusException = false)
1541
    {
1542
        $i = 0;
1543
        $completedStatusList = [
1544
            'completed',
1545
            'passed',
1546
            'succeeded',
1547
            'browsed',
1548
        ];
1549
1550
        if (!$failedStatusException) {
1551
            $completedStatusList[] = 'failed';
1552
        }
1553
1554
        foreach ($this->items as $id => $dummy) {
1555
            // Trying failed and browsed considered "progressed" as well.
1556
            if ($this->items[$id]->status_is($completedStatusList) &&
1557
                $this->items[$id]->get_type() != 'dir'
1558
            ) {
1559
                $i++;
1560
            }
1561
        }
1562
1563
        return $i;
1564
    }
1565
1566
    /**
1567
     * Gets the current item ID.
1568
     *
1569
     * @return int The current learnpath item id
1570
     */
1571
    public function get_current_item_id()
1572
    {
1573
        $current = 0;
1574
        if (!empty($this->current)) {
1575
            $current = (int) $this->current;
1576
        }
1577
1578
        return $current;
1579
    }
1580
1581
    /**
1582
     * Force to get the first learnpath item id.
1583
     *
1584
     * @return int The current learnpath item id
1585
     */
1586
    public function get_first_item_id()
1587
    {
1588
        $current = 0;
1589
        if (is_array($this->ordered_items)) {
1590
            $current = $this->ordered_items[0];
1591
        }
1592
1593
        return $current;
1594
    }
1595
1596
    /**
1597
     * Gets the total number of items available for viewing in this SCORM.
1598
     *
1599
     * @return int The total number of items
1600
     */
1601
    public function get_total_items_count()
1602
    {
1603
        return count($this->items);
1604
    }
1605
1606
    /**
1607
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1608
     *
1609
     * @return int The total no-chapters number of items
1610
     */
1611
    public function getTotalItemsCountWithoutDirs()
1612
    {
1613
        $total = 0;
1614
        $typeListNotToCount = self::getChapterTypes();
1615
        foreach ($this->items as $temp2) {
1616
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1617
                $total++;
1618
            }
1619
        }
1620
1621
        return $total;
1622
    }
1623
1624
    /**
1625
     *  Sets the first element URL.
1626
     */
1627
    public function first()
1628
    {
1629
        if ($this->debug > 0) {
1630
            error_log('In learnpath::first()', 0);
1631
            error_log('$this->last_item_seen '.$this->last_item_seen);
1632
        }
1633
1634
        // Test if the last_item_seen exists and is not a dir.
1635
        if (count($this->ordered_items) == 0) {
1636
            $this->index = 0;
1637
        }
1638
1639
        if (!empty($this->last_item_seen) &&
1640
            !empty($this->items[$this->last_item_seen]) &&
1641
            $this->items[$this->last_item_seen]->get_type() != 'dir'
1642
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1643
            //&& !$this->items[$this->last_item_seen]->is_done()
1644
        ) {
1645
            if ($this->debug > 2) {
1646
                error_log(
1647
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1648
                    $this->items[$this->last_item_seen]->get_type()
1649
                );
1650
            }
1651
            $index = -1;
1652
            foreach ($this->ordered_items as $myindex => $item_id) {
1653
                if ($item_id == $this->last_item_seen) {
1654
                    $index = $myindex;
1655
                    break;
1656
                }
1657
            }
1658
            if ($index == -1) {
1659
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1660
                if ($this->debug > 2) {
1661
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1662
                }
1663
1664
                return false;
1665
            } else {
1666
                $this->last = $this->last_item_seen;
1667
                $this->current = $this->last_item_seen;
1668
                $this->index = $index;
1669
            }
1670
        } else {
1671
            if ($this->debug > 2) {
1672
                error_log('In learnpath::first() - No last item seen', 0);
1673
            }
1674
            $index = 0;
1675
            // Loop through all ordered items and stop at the first item that is
1676
            // not a directory *and* that has not been completed yet.
1677
            while (!empty($this->ordered_items[$index]) &&
1678
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1679
                (
1680
                    $this->items[$this->ordered_items[$index]]->get_type() == 'dir' ||
1681
                    $this->items[$this->ordered_items[$index]]->is_done() === true
1682
                ) && $index < $this->max_ordered_items) {
1683
                $index++;
1684
            }
1685
1686
            $this->last = $this->current;
1687
            // current is
1688
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1689
            $this->index = $index;
1690
            if ($this->debug > 2) {
1691
                error_log('$index '.$index);
1692
                error_log('In learnpath::first() - No last item seen');
1693
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1694
            }
1695
        }
1696
        if ($this->debug > 2) {
1697
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1698
        }
1699
    }
1700
1701
    /**
1702
     * Gets the js library from the database.
1703
     *
1704
     * @return string The name of the javascript library to be used
1705
     */
1706
    public function get_js_lib()
1707
    {
1708
        $lib = '';
1709
        if (!empty($this->js_lib)) {
1710
            $lib = $this->js_lib;
1711
        }
1712
1713
        return $lib;
1714
    }
1715
1716
    /**
1717
     * Gets the learnpath database ID.
1718
     *
1719
     * @return int Learnpath ID in the lp table
1720
     */
1721
    public function get_id()
1722
    {
1723
        if (!empty($this->lp_id)) {
1724
            return (int) $this->lp_id;
1725
        }
1726
1727
        return 0;
1728
    }
1729
1730
    /**
1731
     * Gets the last element URL.
1732
     *
1733
     * @return string URL to load into the viewer
1734
     */
1735
    public function get_last()
1736
    {
1737
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1738
        if (count($this->ordered_items) > 0) {
1739
            $this->index = count($this->ordered_items) - 1;
1740
1741
            return $this->ordered_items[$this->index];
1742
        }
1743
1744
        return false;
1745
    }
1746
1747
    /**
1748
     * Get the last element in the first level.
1749
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1750
     *
1751
     * @return mixed
1752
     */
1753
    public function getLastInFirstLevel()
1754
    {
1755
        try {
1756
            $lastId = Database::getManager()
1757
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1758
                WHERE i.lpId = :lp AND i.parentItemId = 0 AND i.itemType != :type ORDER BY i.displayOrder DESC')
1759
                ->setMaxResults(1)
1760
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1761
                ->getSingleScalarResult();
1762
1763
            return $lastId;
1764
        } catch (Exception $exception) {
1765
            return 0;
1766
        }
1767
    }
1768
1769
    /**
1770
     * Gets the navigation bar for the learnpath display screen.
1771
     *
1772
     * @param string $barId
1773
     *
1774
     * @return string The HTML string to use as a navigation bar
1775
     */
1776
    public function get_navigation_bar($barId = '')
1777
    {
1778
        if (empty($barId)) {
1779
            $barId = 'control-top';
1780
        }
1781
        $lpId = $this->lp_id;
1782
        $mycurrentitemid = $this->get_current_item_id();
1783
1784
        $reportingText = get_lang('Reporting');
1785
        $previousText = get_lang('Previous');
1786
        $nextText = get_lang('Next');
1787
        $fullScreenText = get_lang('Back to normal screen');
1788
1789
        $settings = api_get_configuration_value('lp_view_settings');
1790
        $display = isset($settings['display']) ? $settings['display'] : false;
1791
        $reportingIcon = '
1792
            <a class="icon-toolbar"
1793
                id="stats_link"
1794
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
1795
                onclick="window.parent.API.save_asset(); return true;"
1796
                target="content_name" title="'.$reportingText.'">
1797
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1798
            </a>';
1799
1800
        if (!empty($display)) {
1801
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1802
            if ($showReporting === false) {
1803
                $reportingIcon = '';
1804
            }
1805
        }
1806
1807
        $hideArrows = false;
1808
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1809
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1810
        }
1811
1812
        $previousIcon = '';
1813
        $nextIcon = '';
1814
        if ($hideArrows === false) {
1815
            $previousIcon = '
1816
                <a class="icon-toolbar" id="scorm-previous" href="#"
1817
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1818
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1819
                </a>';
1820
1821
            $nextIcon = '
1822
                <a class="icon-toolbar" id="scorm-next" href="#"
1823
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1824
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1825
                </a>';
1826
        }
1827
1828
        if ($this->mode === 'fullscreen') {
1829
            $navbar = '
1830
                  <span id="'.$barId.'" class="buttons">
1831
                    '.$reportingIcon.'
1832
                    '.$previousIcon.'
1833
                    '.$nextIcon.'
1834
                    <a class="icon-toolbar" id="view-embedded"
1835
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1836
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1837
                    </a>
1838
                  </span>';
1839
        } else {
1840
            $navbar = '
1841
                 <span id="'.$barId.'" class="buttons text-right">
1842
                    '.$reportingIcon.'
1843
                    '.$previousIcon.'
1844
                    '.$nextIcon.'
1845
                </span>';
1846
        }
1847
1848
        return $navbar;
1849
    }
1850
1851
    /**
1852
     * Gets the next resource in queue (url).
1853
     *
1854
     * @return string URL to load into the viewer
1855
     */
1856
    public function get_next_index()
1857
    {
1858
        // TODO
1859
        $index = $this->index;
1860
        $index++;
1861
        while (
1862
            !empty($this->ordered_items[$index]) && ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') &&
1863
            $index < $this->max_ordered_items
1864
        ) {
1865
            $index++;
1866
            if ($index == $this->max_ordered_items) {
1867
                if ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') {
1868
                    return $this->index;
1869
                }
1870
1871
                return $index;
1872
            }
1873
        }
1874
        if (empty($this->ordered_items[$index])) {
1875
            return $this->index;
1876
        }
1877
1878
        return $index;
1879
    }
1880
1881
    /**
1882
     * Gets item_id for the next element.
1883
     *
1884
     * @return int Next item (DB) ID
1885
     */
1886
    public function get_next_item_id()
1887
    {
1888
        $new_index = $this->get_next_index();
1889
        if (!empty($new_index)) {
1890
            if (isset($this->ordered_items[$new_index])) {
1891
                return $this->ordered_items[$new_index];
1892
            }
1893
        }
1894
1895
        return 0;
1896
    }
1897
1898
    /**
1899
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
1900
     *
1901
     * Generally, the package provided is in the form of a zip file, so the function
1902
     * has been written to test a zip file. If not a zip, the function will return the
1903
     * default return value: ''
1904
     *
1905
     * @param string $file_path the path to the file
1906
     * @param string $file_name the original name of the file
1907
     *
1908
     * @return string 'scorm','aicc','scorm2004','dokeos' or '' if the package cannot be recognized
1909
     */
1910
    public static function get_package_type($file_path, $file_name)
1911
    {
1912
        // Get name of the zip file without the extension.
1913
        $file_info = pathinfo($file_name);
1914
        $extension = $file_info['extension']; // Extension only.
1915
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
1916
                'dll',
1917
                'exe',
1918
            ])) {
1919
            return 'oogie';
1920
        }
1921
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
1922
                'dll',
1923
                'exe',
1924
            ])) {
1925
            return 'woogie';
1926
        }
1927
1928
        $zipFile = new PclZip($file_path);
1929
        // Check the zip content (real size and file extension).
1930
        $zipContentArray = $zipFile->listContent();
1931
        $package_type = '';
1932
        $manifest = '';
1933
        $aicc_match_crs = 0;
1934
        $aicc_match_au = 0;
1935
        $aicc_match_des = 0;
1936
        $aicc_match_cst = 0;
1937
1938
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
1939
        if (is_array($zipContentArray) && count($zipContentArray) > 0) {
1940
            foreach ($zipContentArray as $thisContent) {
1941
                if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
1942
                    // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
1943
                } elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) {
1944
                    $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
1945
                    $package_type = 'scorm';
1946
                    break; // Exit the foreach loop.
1947
                } elseif (
1948
                    preg_match('/aicc\//i', $thisContent['filename']) ||
1949
                    in_array(
1950
                        strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
1951
                        ['crs', 'au', 'des', 'cst']
1952
                    )
1953
                ) {
1954
                    $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
1955
                    switch ($ext) {
1956
                        case 'crs':
1957
                            $aicc_match_crs = 1;
1958
                            break;
1959
                        case 'au':
1960
                            $aicc_match_au = 1;
1961
                            break;
1962
                        case 'des':
1963
                            $aicc_match_des = 1;
1964
                            break;
1965
                        case 'cst':
1966
                            $aicc_match_cst = 1;
1967
                            break;
1968
                        default:
1969
                            break;
1970
                    }
1971
                    //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
1972
                } else {
1973
                    $package_type = '';
1974
                }
1975
            }
1976
        }
1977
1978
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
1979
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
1980
            $package_type = 'aicc';
1981
        }
1982
1983
        // Try with chamilo course builder
1984
        if (empty($package_type)) {
1985
            $package_type = 'chamilo';
1986
        }
1987
1988
        return $package_type;
1989
    }
1990
1991
    /**
1992
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
1993
     *
1994
     * @return string URL to load into the viewer
1995
     */
1996
    public function get_previous_index()
1997
    {
1998
        $index = $this->index;
1999
        if (isset($this->ordered_items[$index - 1])) {
2000
            $index--;
2001
            while (isset($this->ordered_items[$index]) &&
2002
                ($this->items[$this->ordered_items[$index]]->get_type() == 'dir')
2003
            ) {
2004
                $index--;
2005
                if ($index < 0) {
2006
                    return $this->index;
2007
                }
2008
            }
2009
        }
2010
2011
        return $index;
2012
    }
2013
2014
    /**
2015
     * Gets item_id for the next element.
2016
     *
2017
     * @return int Previous item (DB) ID
2018
     */
2019
    public function get_previous_item_id()
2020
    {
2021
        $index = $this->get_previous_index();
2022
2023
        return $this->ordered_items[$index];
2024
    }
2025
2026
    /**
2027
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2028
     *
2029
     * @param int    $lpItemId
2030
     * @param string $autostart
2031
     *
2032
     * @return string The mediaplayer HTML
2033
     */
2034
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2035
    {
2036
        $course_id = api_get_course_int_id();
2037
        $_course = api_get_course_info();
2038
        if (empty($_course)) {
2039
            return '';
2040
        }
2041
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2042
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2043
        $lpItemId = (int) $lpItemId;
2044
2045
        /** @var learnpathItem $item */
2046
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
2047
        $itemViewId = 0;
2048
        if ($item) {
2049
            $itemViewId = (int) $item->db_item_view_id;
2050
        }
2051
2052
        // Getting all the information about the item.
2053
        $sql = "SELECT lpi.audio, lpi.item_type, lp_view.status
2054
                FROM $tbl_lp_item as lpi
2055
                INNER JOIN $tbl_lp_item_view as lp_view
2056
                ON (lpi.iid = lp_view.lp_item_id)
2057
                WHERE
2058
                    lp_view.iid = $itemViewId AND
2059
                    lpi.iid = $lpItemId AND
2060
                    lp_view.c_id = $course_id";
2061
        $result = Database::query($sql);
2062
        $row = Database::fetch_assoc($result);
2063
        $output = '';
2064
2065
        if (!empty($row['audio'])) {
2066
            $list = $_SESSION['oLP']->get_toc();
2067
2068
            switch ($row['item_type']) {
2069
                case 'quiz':
2070
                    $type_quiz = false;
2071
                    foreach ($list as $toc) {
2072
                        if ($toc['id'] == $_SESSION['oLP']->current) {
2073
                            $type_quiz = true;
2074
                        }
2075
                    }
2076
2077
                    if ($type_quiz) {
2078
                        if ($_SESSION['oLP']->prevent_reinit == 1) {
2079
                            $autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
2080
                        } else {
2081
                            $autostart_audio = $autostart;
2082
                        }
2083
                    }
2084
                    break;
2085
                case TOOL_READOUT_TEXT:;
2086
                    $autostart_audio = 'false';
2087
                    break;
2088
                default:
2089
                    $autostart_audio = 'true';
2090
            }
2091
2092
            $courseInfo = api_get_course_info();
2093
            $audio = $row['audio'];
2094
2095
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio;
2096
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio.'?'.api_get_cidreq();
2097
2098
            if (!file_exists($file)) {
2099
                $lpPathInfo = $_SESSION['oLP']->generate_lp_folder(api_get_course_info());
2100
                $file = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio;
2101
                $url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio.'?'.api_get_cidreq();
2102
            }
2103
2104
            $player = Display::getMediaPlayer(
2105
                $file,
2106
                [
2107
                    'id' => 'lp_audio_media_player',
2108
                    'url' => $url,
2109
                    'autoplay' => $autostart_audio,
2110
                    'width' => '100%',
2111
                ]
2112
            );
2113
2114
            // The mp3 player.
2115
            $output = '<div id="container">';
2116
            $output .= $player;
2117
            $output .= '</div>';
2118
        }
2119
2120
        return $output;
2121
    }
2122
2123
    /**
2124
     * @param int   $studentId
2125
     * @param int   $prerequisite
2126
     * @param array $courseInfo
2127
     * @param int   $sessionId
2128
     *
2129
     * @return bool
2130
     */
2131
    public static function isBlockedByPrerequisite(
2132
        $studentId,
2133
        $prerequisite,
2134
        $courseInfo,
2135
        $sessionId
2136
    ) {
2137
        if (empty($courseInfo)) {
2138
            return false;
2139
        }
2140
2141
        $courseId = $courseInfo['real_id'];
2142
2143
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2144
        if ($allow) {
2145
            if (api_is_allowed_to_edit() ||
2146
                api_is_platform_admin(true) ||
2147
                api_is_drh() ||
2148
                api_is_coach($sessionId, $courseId, false)
2149
            ) {
2150
                return false;
2151
            }
2152
        }
2153
2154
        $isBlocked = false;
2155
        if (!empty($prerequisite)) {
2156
            $progress = self::getProgress(
2157
                $prerequisite,
2158
                $studentId,
2159
                $courseId,
2160
                $sessionId
2161
            );
2162
            if ($progress < 100) {
2163
                $isBlocked = true;
2164
            }
2165
2166
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2167
                // Block if it does not exceed minimum time
2168
                // Minimum time (in minutes) to pass the learning path
2169
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
2170
2171
                if ($accumulateWorkTime > 0) {
2172
                    // Total time in course (sum of times in learning paths from course)
2173
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
2174
2175
                    // Connect with the plugin_licences_course_session table
2176
                    // which indicates what percentage of the time applies
2177
                    // Minimum connection percentage
2178
                    $perc = 100;
2179
                    // Time from the course
2180
                    $tc = $accumulateWorkTimeTotal;
2181
2182
                    // Percentage of the learning paths
2183
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
2184
                    // Minimum time for each learning path
2185
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
2186
2187
                    // Spent time (in seconds) so far in the learning path
2188
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
2189
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
2190
2191
                    if ($lpTime < ($accumulateWorkTime * 60)) {
2192
                        $isBlocked = true;
2193
                    }
2194
                }
2195
            }
2196
        }
2197
2198
        return $isBlocked;
2199
    }
2200
2201
    /**
2202
     * Checks if the learning path is visible for student after the progress
2203
     * of its prerequisite is completed, considering the time availability and
2204
     * the LP visibility.
2205
     *
2206
     * @param int   $lp_id
2207
     * @param int   $student_id
2208
     * @param array $courseInfo
2209
     * @param int   $sessionId
2210
     *
2211
     * @return bool
2212
     */
2213
    public static function is_lp_visible_for_student(
2214
        CLp $lp,
2215
        $student_id,
2216
        $courseInfo = [],
2217
        $sessionId = 0
2218
    ) {
2219
        $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
2220
        $sessionId = (int) $sessionId;
2221
2222
        if (empty($courseInfo)) {
2223
            return false;
2224
        }
2225
2226
        if (empty($sessionId)) {
2227
            $sessionId = api_get_session_id();
2228
        }
2229
2230
        $courseId = $courseInfo['real_id'];
2231
2232
        /*$itemInfo = api_get_item_property_info(
2233
            $courseId,
2234
            TOOL_LEARNPATH,
2235
            $lp_id,
2236
            $sessionId
2237
        );*/
2238
2239
        $visibility = $lp->isVisible($courseInfo['entity'], api_get_session_entity($sessionId));
2240
        // If the item was deleted.
2241
        if ($visibility === false) {
2242
            return false;
2243
        }
2244
2245
        $lp_id = $lp->getIid();
2246
        // @todo remove this query and load the row info as a parameter
2247
        $table = Database::get_course_table(TABLE_LP_MAIN);
2248
        // Get current prerequisite
2249
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on, category_id
2250
                FROM $table
2251
                WHERE iid = $lp_id";
2252
        $rs = Database::query($sql);
2253
        $now = time();
2254
        if (Database::num_rows($rs) > 0) {
2255
            $row = Database::fetch_array($rs, 'ASSOC');
2256
2257
            if (!empty($row['category_id'])) {
2258
                $em = Database::getManager();
2259
                $category = $em->getRepository('ChamiloCourseBundle:CLpCategory')->find($row['category_id']);
2260
                if (self::categoryIsVisibleForStudent($category, api_get_user_entity($student_id)) === false) {
2261
                    return false;
2262
                }
2263
            }
2264
2265
            $prerequisite = $row['prerequisite'];
2266
            $is_visible = true;
2267
2268
            $isBlocked = self::isBlockedByPrerequisite(
2269
                $student_id,
2270
                $prerequisite,
2271
                $courseInfo,
2272
                $sessionId
2273
            );
2274
2275
            if ($isBlocked) {
2276
                $is_visible = false;
2277
            }
2278
2279
            // Also check the time availability of the LP
2280
            if ($is_visible) {
2281
                // Adding visibility restrictions
2282
                if (!empty($row['publicated_on'])) {
2283
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2284
                        $is_visible = false;
2285
                    }
2286
                }
2287
                // Blocking empty start times see BT#2800
2288
                global $_custom;
2289
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2290
                    $_custom['lps_hidden_when_no_start_date']
2291
                ) {
2292
                    if (empty($row['publicated_on'])) {
2293
                        $is_visible = false;
2294
                    }
2295
                }
2296
2297
                if (!empty($row['expired_on'])) {
2298
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2299
                        $is_visible = false;
2300
                    }
2301
                }
2302
            }
2303
2304
            if ($is_visible) {
2305
                $subscriptionSettings = self::getSubscriptionSettings();
2306
2307
                // Check if the subscription users/group to a LP is ON
2308
                if (isset($row['subscribe_users']) && $row['subscribe_users'] == 1 &&
2309
                    $subscriptionSettings['allow_add_users_to_lp'] === true
2310
                ) {
2311
                    // Try group
2312
                    $is_visible = false;
2313
                    // Checking only the user visibility
2314
                    $userVisibility = api_get_item_visibility(
2315
                        $courseInfo,
2316
                        'learnpath',
2317
                        $row['id'],
2318
                        $sessionId,
2319
                        $student_id,
2320
                        'LearnpathSubscription'
2321
                    );
2322
2323
                    if ($userVisibility == 1) {
2324
                        $is_visible = true;
2325
                    } else {
2326
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
2327
                        if (!empty($userGroups)) {
2328
                            foreach ($userGroups as $groupInfo) {
2329
                                $groupId = $groupInfo['iid'];
2330
                                $userVisibility = api_get_item_visibility(
2331
                                    $courseInfo,
2332
                                    'learnpath',
2333
                                    $row['id'],
2334
                                    $sessionId,
2335
                                    null,
2336
                                    'LearnpathSubscription',
2337
                                    $groupId
2338
                                );
2339
2340
                                if ($userVisibility == 1) {
2341
                                    $is_visible = true;
2342
                                    break;
2343
                                }
2344
                            }
2345
                        }
2346
                    }
2347
                }
2348
            }
2349
2350
            return $is_visible;
2351
        }
2352
2353
        return false;
2354
    }
2355
2356
    /**
2357
     * @param int $lpId
2358
     * @param int $userId
2359
     * @param int $courseId
2360
     * @param int $sessionId
2361
     *
2362
     * @return int
2363
     */
2364
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2365
    {
2366
        $lpId = (int) $lpId;
2367
        $userId = (int) $userId;
2368
        $courseId = (int) $courseId;
2369
        $sessionId = (int) $sessionId;
2370
2371
        $sessionCondition = api_get_session_condition($sessionId);
2372
        $table = Database::get_course_table(TABLE_LP_VIEW);
2373
        $sql = "SELECT progress FROM $table
2374
                WHERE
2375
                    c_id = $courseId AND
2376
                    lp_id = $lpId AND
2377
                    user_id = $userId $sessionCondition ";
2378
        $res = Database::query($sql);
2379
2380
        $progress = 0;
2381
        if (Database::num_rows($res) > 0) {
2382
            $row = Database::fetch_array($res);
2383
            $progress = (int) $row['progress'];
2384
        }
2385
2386
        return $progress;
2387
    }
2388
2389
    /**
2390
     * @param array $lpList
2391
     * @param int   $userId
2392
     * @param int   $courseId
2393
     * @param int   $sessionId
2394
     *
2395
     * @return array
2396
     */
2397
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2398
    {
2399
        $lpList = array_map('intval', $lpList);
2400
        if (empty($lpList)) {
2401
            return [];
2402
        }
2403
2404
        $lpList = implode("','", $lpList);
2405
2406
        $userId = (int) $userId;
2407
        $courseId = (int) $courseId;
2408
        $sessionId = (int) $sessionId;
2409
2410
        $sessionCondition = api_get_session_condition($sessionId);
2411
        $table = Database::get_course_table(TABLE_LP_VIEW);
2412
        $sql = "SELECT lp_id, progress FROM $table
2413
                WHERE
2414
                    c_id = $courseId AND
2415
                    lp_id IN ('".$lpList."') AND
2416
                    user_id = $userId $sessionCondition ";
2417
        $res = Database::query($sql);
2418
2419
        if (Database::num_rows($res) > 0) {
2420
            $list = [];
2421
            while ($row = Database::fetch_array($res)) {
2422
                $list[$row['lp_id']] = $row['progress'];
2423
            }
2424
2425
            return $list;
2426
        }
2427
2428
        return [];
2429
    }
2430
2431
    /**
2432
     * Displays a progress bar
2433
     * completed so far.
2434
     *
2435
     * @param int    $percentage Progress value to display
2436
     * @param string $text_add   Text to display near the progress value
2437
     *
2438
     * @return string HTML string containing the progress bar
2439
     */
2440
    public static function get_progress_bar($percentage = -1, $text_add = '')
2441
    {
2442
        $text = $percentage.$text_add;
2443
        $output = '<div class="progress">
2444
            <div id="progress_bar_value"
2445
                class="progress-bar progress-bar-success" role="progressbar"
2446
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2447
            '.$text.'
2448
            </div>
2449
        </div>';
2450
2451
        return $output;
2452
    }
2453
2454
    /**
2455
     * @param string $mode can be '%' or 'abs'
2456
     *                     otherwise this value will be used $this->progress_bar_mode
2457
     *
2458
     * @return string
2459
     */
2460
    public function getProgressBar($mode = null)
2461
    {
2462
        list($percentage, $text_add) = $this->get_progress_bar_text($mode);
2463
2464
        return self::get_progress_bar($percentage, $text_add);
2465
    }
2466
2467
    /**
2468
     * Gets the progress bar info to display inside the progress bar.
2469
     * Also used by scorm_api.php.
2470
     *
2471
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2472
     *                     we display a number of completed elements per total elements
2473
     * @param int    $add  Additional steps to fake as completed
2474
     *
2475
     * @return array Percentage or number and symbol (% or /xx)
2476
     */
2477
    public function get_progress_bar_text($mode = '', $add = 0)
2478
    {
2479
        if (empty($mode)) {
2480
            $mode = $this->progress_bar_mode;
2481
        }
2482
        $total_items = $this->getTotalItemsCountWithoutDirs();
2483
        $completeItems = $this->get_complete_items_count();
2484
        if ($add != 0) {
2485
            $completeItems += $add;
2486
        }
2487
        $text = '';
2488
        if ($completeItems > $total_items) {
2489
            $completeItems = $total_items;
2490
        }
2491
        $percentage = 0;
2492
        if ($mode == '%') {
2493
            if ($total_items > 0) {
2494
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2495
            }
2496
            $percentage = number_format($percentage, 0);
2497
            $text = '%';
2498
        } elseif ($mode === 'abs') {
2499
            $percentage = $completeItems;
2500
            $text = '/'.$total_items;
2501
        }
2502
2503
        return [
2504
            $percentage,
2505
            $text,
2506
        ];
2507
    }
2508
2509
    /**
2510
     * Gets the progress bar mode.
2511
     *
2512
     * @return string The progress bar mode attribute
2513
     */
2514
    public function get_progress_bar_mode()
2515
    {
2516
        if (!empty($this->progress_bar_mode)) {
2517
            return $this->progress_bar_mode;
2518
        }
2519
2520
        return '%';
2521
    }
2522
2523
    /**
2524
     * Gets the learnpath theme (remote or local).
2525
     *
2526
     * @return string Learnpath theme
2527
     */
2528
    public function get_theme()
2529
    {
2530
        if (!empty($this->theme)) {
2531
            return $this->theme;
2532
        }
2533
2534
        return '';
2535
    }
2536
2537
    /**
2538
     * Gets the learnpath session id.
2539
     *
2540
     * @return int
2541
     */
2542
    public function get_lp_session_id()
2543
    {
2544
        if (!empty($this->lp_session_id)) {
2545
            return (int) $this->lp_session_id;
2546
        }
2547
2548
        return 0;
2549
    }
2550
2551
    /**
2552
     * Gets the learnpath image.
2553
     *
2554
     * @return string Web URL of the LP image
2555
     */
2556
    public function get_preview_image()
2557
    {
2558
        if (!empty($this->preview_image)) {
2559
            return $this->preview_image;
2560
        }
2561
2562
        return '';
2563
    }
2564
2565
    /**
2566
     * @param string $size
2567
     * @param string $path_type
2568
     *
2569
     * @return bool|string
2570
     */
2571
    public function get_preview_image_path($size = null, $path_type = 'web')
2572
    {
2573
        $preview_image = $this->get_preview_image();
2574
        if (isset($preview_image) && !empty($preview_image)) {
2575
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2576
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2577
2578
            if (isset($size)) {
2579
                $info = pathinfo($preview_image);
2580
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2581
2582
                if (file_exists($image_sys_path.$image_custom_size)) {
2583
                    if ($path_type == 'web') {
2584
                        return $image_path.$image_custom_size;
2585
                    } else {
2586
                        return $image_sys_path.$image_custom_size;
2587
                    }
2588
                }
2589
            } else {
2590
                if ($path_type == 'web') {
2591
                    return $image_path.$preview_image;
2592
                } else {
2593
                    return $image_sys_path.$preview_image;
2594
                }
2595
            }
2596
        }
2597
2598
        return false;
2599
    }
2600
2601
    /**
2602
     * Gets the learnpath author.
2603
     *
2604
     * @return string LP's author
2605
     */
2606
    public function get_author()
2607
    {
2608
        if (!empty($this->author)) {
2609
            return $this->author;
2610
        }
2611
2612
        return '';
2613
    }
2614
2615
    /**
2616
     * Gets hide table of contents.
2617
     *
2618
     * @return int
2619
     */
2620
    public function getHideTableOfContents()
2621
    {
2622
        return (int) $this->hide_toc_frame;
2623
    }
2624
2625
    /**
2626
     * Generate a new prerequisites string for a given item. If this item was a sco and
2627
     * its prerequisites were strings (instead of IDs), then transform those strings into
2628
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2629
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2630
     * same rule as the scormExport() method.
2631
     *
2632
     * @param int $item_id Item ID
2633
     *
2634
     * @return string Prerequisites string ready for the export as SCORM
2635
     */
2636
    public function get_scorm_prereq_string($item_id)
2637
    {
2638
        if ($this->debug > 0) {
2639
            error_log('In learnpath::get_scorm_prereq_string()');
2640
        }
2641
        if (!is_object($this->items[$item_id])) {
2642
            return false;
2643
        }
2644
        /** @var learnpathItem $oItem */
2645
        $oItem = $this->items[$item_id];
2646
        $prereq = $oItem->get_prereq_string();
2647
2648
        if (empty($prereq)) {
2649
            return '';
2650
        }
2651
        if (preg_match('/^\d+$/', $prereq) &&
2652
            isset($this->items[$prereq]) &&
2653
            is_object($this->items[$prereq])
2654
        ) {
2655
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2656
            // then simply return it (with the ITEM_ prefix).
2657
            //return 'ITEM_' . $prereq;
2658
            return $this->items[$prereq]->ref;
2659
        } else {
2660
            if (isset($this->refs_list[$prereq])) {
2661
                // It's a simple string item from which the ID can be found in the refs list,
2662
                // so we can transform it directly to an ID for export.
2663
                return $this->items[$this->refs_list[$prereq]]->ref;
2664
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2665
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2666
            } else {
2667
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2668
                // and replace them, one by one, by the internal IDs (chamilo db)
2669
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2670
                // by a space as well.
2671
                $find = [
2672
                    '&',
2673
                    '|',
2674
                    '~',
2675
                    '=',
2676
                    '<>',
2677
                    '{',
2678
                    '}',
2679
                    '*',
2680
                    '(',
2681
                    ')',
2682
                ];
2683
                $replace = [
2684
                    ' ',
2685
                    ' ',
2686
                    ' ',
2687
                    ' ',
2688
                    ' ',
2689
                    ' ',
2690
                    ' ',
2691
                    ' ',
2692
                    ' ',
2693
                    ' ',
2694
                ];
2695
                $prereq_mod = str_replace($find, $replace, $prereq);
2696
                $ids = explode(' ', $prereq_mod);
2697
                foreach ($ids as $id) {
2698
                    $id = trim($id);
2699
                    if (isset($this->refs_list[$id])) {
2700
                        $prereq = preg_replace(
2701
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2702
                            'ITEM_'.$this->refs_list[$id],
2703
                            $prereq
2704
                        );
2705
                    }
2706
                }
2707
2708
                return $prereq;
2709
            }
2710
        }
2711
    }
2712
2713
    /**
2714
     * Returns the XML DOM document's node.
2715
     *
2716
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2717
     * @param string   $id       The identifier to look for
2718
     *
2719
     * @return mixed The reference to the element found with that identifier. False if not found
2720
     */
2721
    public function get_scorm_xml_node(&$children, $id)
2722
    {
2723
        for ($i = 0; $i < $children->length; $i++) {
2724
            $item_temp = $children->item($i);
2725
            if ($item_temp->nodeName == 'item') {
2726
                if ($item_temp->getAttribute('identifier') == $id) {
2727
                    return $item_temp;
2728
                }
2729
            }
2730
            $subchildren = $item_temp->childNodes;
2731
            if ($subchildren && $subchildren->length > 0) {
2732
                $val = $this->get_scorm_xml_node($subchildren, $id);
2733
                if (is_object($val)) {
2734
                    return $val;
2735
                }
2736
            }
2737
        }
2738
2739
        return false;
2740
    }
2741
2742
    /**
2743
     * Gets the status list for all LP's items.
2744
     *
2745
     * @return array Array of [index] => [item ID => current status]
2746
     */
2747
    public function get_items_status_list()
2748
    {
2749
        $list = [];
2750
        foreach ($this->ordered_items as $item_id) {
2751
            $list[] = [
2752
                $item_id => $this->items[$item_id]->get_status(),
2753
            ];
2754
        }
2755
2756
        return $list;
2757
    }
2758
2759
    /**
2760
     * Return the number of interactions for the given learnpath Item View ID.
2761
     * This method can be used as static.
2762
     *
2763
     * @param int $lp_iv_id  Item View ID
2764
     * @param int $course_id course id
2765
     *
2766
     * @return int
2767
     */
2768
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2769
    {
2770
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2771
        $lp_iv_id = (int) $lp_iv_id;
2772
        $course_id = (int) $course_id;
2773
2774
        $sql = "SELECT count(*) FROM $table
2775
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2776
        $res = Database::query($sql);
2777
        $num = 0;
2778
        if (Database::num_rows($res)) {
2779
            $row = Database::fetch_array($res);
2780
            $num = $row[0];
2781
        }
2782
2783
        return $num;
2784
    }
2785
2786
    /**
2787
     * Return the interactions as an array for the given lp_iv_id.
2788
     * This method can be used as static.
2789
     *
2790
     * @param int $lp_iv_id Learnpath Item View ID
2791
     *
2792
     * @return array
2793
     *
2794
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2795
     */
2796
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2797
    {
2798
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2799
        $list = [];
2800
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2801
        $lp_iv_id = (int) $lp_iv_id;
2802
2803
        if (empty($lp_iv_id) || empty($course_id)) {
2804
            return [];
2805
        }
2806
2807
        $sql = "SELECT * FROM $table
2808
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2809
                ORDER BY order_id ASC";
2810
        $res = Database::query($sql);
2811
        $num = Database::num_rows($res);
2812
        if ($num > 0) {
2813
            $list[] = [
2814
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2815
                'id' => api_htmlentities(get_lang('Interaction ID'), ENT_QUOTES),
2816
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2817
                'time' => api_htmlentities(get_lang('Time (finished at...)'), ENT_QUOTES),
2818
                'correct_responses' => api_htmlentities(get_lang('Correct answers'), ENT_QUOTES),
2819
                'student_response' => api_htmlentities(get_lang('Learner answers'), ENT_QUOTES),
2820
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2821
                'latency' => api_htmlentities(get_lang('Time spent'), ENT_QUOTES),
2822
                'student_response_formatted' => '',
2823
            ];
2824
            while ($row = Database::fetch_array($res)) {
2825
                $studentResponseFormatted = urldecode($row['student_response']);
2826
                $content_student_response = explode('__|', $studentResponseFormatted);
2827
                if (count($content_student_response) > 0) {
2828
                    if (count($content_student_response) >= 3) {
2829
                        // Pop the element off the end of array.
2830
                        array_pop($content_student_response);
2831
                    }
2832
                    $studentResponseFormatted = implode(',', $content_student_response);
2833
                }
2834
2835
                $list[] = [
2836
                    'order_id' => $row['order_id'] + 1,
2837
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2838
                    'type' => $row['interaction_type'],
2839
                    'time' => $row['completion_time'],
2840
                    'correct_responses' => '', // Hide correct responses from students.
2841
                    'student_response' => $row['student_response'],
2842
                    'result' => $row['result'],
2843
                    'latency' => $row['latency'],
2844
                    'student_response_formatted' => $studentResponseFormatted,
2845
                ];
2846
            }
2847
        }
2848
2849
        return $list;
2850
    }
2851
2852
    /**
2853
     * Return the number of objectives for the given learnpath Item View ID.
2854
     * This method can be used as static.
2855
     *
2856
     * @param int $lp_iv_id  Item View ID
2857
     * @param int $course_id Course ID
2858
     *
2859
     * @return int Number of objectives
2860
     */
2861
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2862
    {
2863
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2864
        $course_id = (int) $course_id;
2865
        $lp_iv_id = (int) $lp_iv_id;
2866
        $sql = "SELECT count(*) FROM $table
2867
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2868
        //@todo seems that this always returns 0
2869
        $res = Database::query($sql);
2870
        $num = 0;
2871
        if (Database::num_rows($res)) {
2872
            $row = Database::fetch_array($res);
2873
            $num = $row[0];
2874
        }
2875
2876
        return $num;
2877
    }
2878
2879
    /**
2880
     * Return the objectives as an array for the given lp_iv_id.
2881
     * This method can be used as static.
2882
     *
2883
     * @param int $lpItemViewId Learnpath Item View ID
2884
     * @param int $course_id
2885
     *
2886
     * @return array
2887
     *
2888
     * @todo    Translate labels
2889
     */
2890
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
2891
    {
2892
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2893
        $lpItemViewId = (int) $lpItemViewId;
2894
2895
        if (empty($course_id) || empty($lpItemViewId)) {
2896
            return [];
2897
        }
2898
2899
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2900
        $sql = "SELECT * FROM $table
2901
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
2902
                ORDER BY order_id ASC";
2903
        $res = Database::query($sql);
2904
        $num = Database::num_rows($res);
2905
        $list = [];
2906
        if ($num > 0) {
2907
            $list[] = [
2908
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2909
                'objective_id' => api_htmlentities(get_lang('Objective ID'), ENT_QUOTES),
2910
                'score_raw' => api_htmlentities(get_lang('Objective raw score'), ENT_QUOTES),
2911
                'score_max' => api_htmlentities(get_lang('Objective max score'), ENT_QUOTES),
2912
                'score_min' => api_htmlentities(get_lang('Objective min score'), ENT_QUOTES),
2913
                'status' => api_htmlentities(get_lang('Objective status'), ENT_QUOTES),
2914
            ];
2915
            while ($row = Database::fetch_array($res)) {
2916
                $list[] = [
2917
                    'order_id' => $row['order_id'] + 1,
2918
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
2919
                    'score_raw' => $row['score_raw'],
2920
                    'score_max' => $row['score_max'],
2921
                    'score_min' => $row['score_min'],
2922
                    'status' => $row['status'],
2923
                ];
2924
            }
2925
        }
2926
2927
        return $list;
2928
    }
2929
2930
    /**
2931
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
2932
     * used by get_html_toc() to be ready to display.
2933
     *
2934
     * @return array TOC as a table with 4 elements per row: title, link, status and level
2935
     */
2936
    public function get_toc()
2937
    {
2938
        $toc = [];
2939
        foreach ($this->ordered_items as $item_id) {
2940
            // TODO: Change this link generation and use new function instead.
2941
            $toc[] = [
2942
                'id' => $item_id,
2943
                'title' => $this->items[$item_id]->get_title(),
2944
                'status' => $this->items[$item_id]->get_status(),
2945
                'level' => $this->items[$item_id]->get_level(),
2946
                'type' => $this->items[$item_id]->get_type(),
2947
                'description' => $this->items[$item_id]->get_description(),
2948
                'path' => $this->items[$item_id]->get_path(),
2949
                'parent' => $this->items[$item_id]->get_parent(),
2950
            ];
2951
        }
2952
2953
        return $toc;
2954
    }
2955
2956
    /**
2957
     * Generate and return the table of contents for this learnpath. The JS
2958
     * table returned is used inside of scorm_api.php.
2959
     *
2960
     * @param string $varname
2961
     *
2962
     * @return string A JS array variable construction
2963
     */
2964
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
2965
    {
2966
        $toc = $varname.' = new Array();';
2967
        foreach ($this->ordered_items as $item_id) {
2968
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
2969
        }
2970
2971
        return $toc;
2972
    }
2973
2974
    /**
2975
     * Gets the learning path type.
2976
     *
2977
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
2978
     *
2979
     * @return mixed Type ID or name, depending on the parameter
2980
     */
2981
    public function get_type($get_name = false)
2982
    {
2983
        $res = false;
2984
        if (!empty($this->type) && (!$get_name)) {
2985
            $res = $this->type;
2986
        }
2987
2988
        return $res;
2989
    }
2990
2991
    /**
2992
     * Gets the learning path type as static method.
2993
     *
2994
     * @param int $lp_id
2995
     *
2996
     * @return mixed Type ID or name, depending on the parameter
2997
     */
2998
    public static function get_type_static($lp_id = 0)
2999
    {
3000
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3001
        $lp_id = (int) $lp_id;
3002
        $sql = "SELECT lp_type FROM $tbl_lp
3003
                WHERE iid = $lp_id";
3004
        $res = Database::query($sql);
3005
        if ($res === false) {
3006
            return null;
3007
        }
3008
        if (Database::num_rows($res) <= 0) {
3009
            return null;
3010
        }
3011
        $row = Database::fetch_array($res);
3012
3013
        return $row['lp_type'];
3014
    }
3015
3016
    /**
3017
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3018
     * This method can be used as abstract and is recursive.
3019
     *
3020
     * @param int $lp        Learnpath ID
3021
     * @param int $parent    Parent ID of the items to look for
3022
     * @param int $course_id
3023
     *
3024
     * @return array Ordered list of item IDs (empty array on error)
3025
     */
3026
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3027
    {
3028
        if (empty($course_id)) {
3029
            $course_id = api_get_course_int_id();
3030
        } else {
3031
            $course_id = (int) $course_id;
3032
        }
3033
        $list = [];
3034
3035
        if (empty($lp)) {
3036
            return $list;
3037
        }
3038
3039
        $lp = (int) $lp;
3040
        $parent = (int) $parent;
3041
3042
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3043
        $sql = "SELECT iid FROM $tbl_lp_item
3044
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3045
                ORDER BY display_order";
3046
3047
        $res = Database::query($sql);
3048
        while ($row = Database::fetch_array($res)) {
3049
            $sublist = self::get_flat_ordered_items_list(
3050
                $lp,
3051
                $row['iid'],
3052
                $course_id
3053
            );
3054
            $list[] = $row['iid'];
3055
            foreach ($sublist as $item) {
3056
                $list[] = $item;
3057
            }
3058
        }
3059
3060
        return $list;
3061
    }
3062
3063
    /**
3064
     * @return array
3065
     */
3066
    public static function getChapterTypes()
3067
    {
3068
        return [
3069
            'dir',
3070
        ];
3071
    }
3072
3073
    /**
3074
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3075
     *
3076
     * @param $tree
3077
     *
3078
     * @return array HTML TOC ready to display
3079
     */
3080
    public function getParentToc($tree)
3081
    {
3082
        if (empty($tree)) {
3083
            $tree = $this->get_toc();
3084
        }
3085
        $dirTypes = self::getChapterTypes();
3086
        $myCurrentId = $this->get_current_item_id();
3087
        $listParent = [];
3088
        $listChildren = [];
3089
        $listNotParent = [];
3090
        $list = [];
3091
        foreach ($tree as $subtree) {
3092
            if (in_array($subtree['type'], $dirTypes)) {
3093
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3094
                $subtree['children'] = $listChildren;
3095
                if (!empty($subtree['children'])) {
3096
                    foreach ($subtree['children'] as $subItem) {
3097
                        if ($subItem['id'] == $this->current) {
3098
                            $subtree['parent_current'] = 'in';
3099
                            $subtree['current'] = 'on';
3100
                        }
3101
                    }
3102
                }
3103
                $listParent[] = $subtree;
3104
            }
3105
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
3106
                $classStatus = [
3107
                    'not attempted' => 'scorm_not_attempted',
3108
                    'incomplete' => 'scorm_not_attempted',
3109
                    'failed' => 'scorm_failed',
3110
                    'completed' => 'scorm_completed',
3111
                    'passed' => 'scorm_completed',
3112
                    'succeeded' => 'scorm_completed',
3113
                    'browsed' => 'scorm_completed',
3114
                ];
3115
3116
                if (isset($classStatus[$subtree['status']])) {
3117
                    $cssStatus = $classStatus[$subtree['status']];
3118
                }
3119
3120
                $title = Security::remove_XSS($subtree['title']);
3121
                unset($subtree['title']);
3122
3123
                if (empty($title)) {
3124
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3125
                }
3126
                $classStyle = null;
3127
                if ($subtree['id'] == $this->current) {
3128
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3129
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3130
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3131
                }
3132
                $subtree['title'] = $title;
3133
                $subtree['class'] = $classStyle.' '.$cssStatus;
3134
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3135
                $subtree['current_id'] = $myCurrentId;
3136
                $listNotParent[] = $subtree;
3137
            }
3138
        }
3139
3140
        $list['are_parents'] = $listParent;
3141
        $list['not_parents'] = $listNotParent;
3142
3143
        return $list;
3144
    }
3145
3146
    /**
3147
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3148
     *
3149
     * @param array $tree
3150
     * @param int   $id
3151
     * @param bool  $parent
3152
     *
3153
     * @return array HTML TOC ready to display
3154
     */
3155
    public function getChildrenToc($tree, $id, $parent = true)
3156
    {
3157
        if (empty($tree)) {
3158
            $tree = $this->get_toc();
3159
        }
3160
3161
        $dirTypes = self::getChapterTypes();
3162
        $currentItemId = $this->get_current_item_id();
3163
        $list = [];
3164
        $classStatus = [
3165
            'not attempted' => 'scorm_not_attempted',
3166
            'incomplete' => 'scorm_not_attempted',
3167
            'failed' => 'scorm_failed',
3168
            'completed' => 'scorm_completed',
3169
            'passed' => 'scorm_completed',
3170
            'succeeded' => 'scorm_completed',
3171
            'browsed' => 'scorm_completed',
3172
        ];
3173
3174
        foreach ($tree as $subtree) {
3175
            $subtree['tree'] = null;
3176
3177
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3178
                if ($subtree['id'] == $this->current) {
3179
                    $subtree['current'] = 'active';
3180
                } else {
3181
                    $subtree['current'] = null;
3182
                }
3183
                if (isset($classStatus[$subtree['status']])) {
3184
                    $cssStatus = $classStatus[$subtree['status']];
3185
                }
3186
3187
                $title = Security::remove_XSS($subtree['title']);
3188
                unset($subtree['title']);
3189
                if (empty($title)) {
3190
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3191
                }
3192
3193
                $classStyle = null;
3194
                if ($subtree['id'] == $this->current) {
3195
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3196
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3197
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3198
                }
3199
3200
                if (in_array($subtree['type'], $dirTypes)) {
3201
                    $subtree['title'] = stripslashes($title);
3202
                } else {
3203
                    $subtree['title'] = $title;
3204
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3205
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3206
                    $subtree['current_id'] = $currentItemId;
3207
                }
3208
                $list[] = $subtree;
3209
            }
3210
        }
3211
3212
        return $list;
3213
    }
3214
3215
    /**
3216
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3217
     *
3218
     * @param array $toc_list
3219
     *
3220
     * @return array HTML TOC ready to display
3221
     */
3222
    public function getListArrayToc($toc_list = [])
3223
    {
3224
        if (empty($toc_list)) {
3225
            $toc_list = $this->get_toc();
3226
        }
3227
        // Temporary variables.
3228
        $currentItemId = $this->get_current_item_id();
3229
        $list = [];
3230
        $arrayList = [];
3231
        $classStatus = [
3232
            'not attempted' => 'scorm_not_attempted',
3233
            'incomplete' => 'scorm_not_attempted',
3234
            'failed' => 'scorm_failed',
3235
            'completed' => 'scorm_completed',
3236
            'passed' => 'scorm_completed',
3237
            'succeeded' => 'scorm_completed',
3238
            'browsed' => 'scorm_completed',
3239
        ];
3240
3241
        foreach ($toc_list as $item) {
3242
            $list['id'] = $item['id'];
3243
            $list['status'] = $item['status'];
3244
            $cssStatus = null;
3245
3246
            if (isset($classStatus[$item['status']])) {
3247
                $cssStatus = $classStatus[$item['status']];
3248
            }
3249
3250
            $classStyle = ' ';
3251
            $dirTypes = self::getChapterTypes();
3252
3253
            if (in_array($item['type'], $dirTypes)) {
3254
                $classStyle = 'scorm_item_section ';
3255
            }
3256
            if ($item['id'] == $this->current) {
3257
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3258
            } elseif (!in_array($item['type'], $dirTypes)) {
3259
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3260
            }
3261
            $title = $item['title'];
3262
            if (empty($title)) {
3263
                $title = self::rl_get_resource_name(
3264
                    api_get_course_id(),
3265
                    $this->get_id(),
3266
                    $item['id']
3267
                );
3268
            }
3269
            $title = Security::remove_XSS($item['title']);
3270
3271
            if (empty($item['description'])) {
3272
                $list['description'] = $title;
3273
            } else {
3274
                $list['description'] = $item['description'];
3275
            }
3276
3277
            $list['class'] = $classStyle.' '.$cssStatus;
3278
            $list['level'] = $item['level'];
3279
            $list['type'] = $item['type'];
3280
3281
            if (in_array($item['type'], $dirTypes)) {
3282
                $list['css_level'] = 'level_'.$item['level'];
3283
            } else {
3284
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3285
            }
3286
3287
            if (in_array($item['type'], $dirTypes)) {
3288
                $list['title'] = stripslashes($title);
3289
            } else {
3290
                $list['title'] = stripslashes($title);
3291
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3292
                $list['current_id'] = $currentItemId;
3293
            }
3294
            $arrayList[] = $list;
3295
        }
3296
3297
        return $arrayList;
3298
    }
3299
3300
    /**
3301
     * Returns an HTML-formatted string ready to display with teacher buttons
3302
     * in LP view menu.
3303
     *
3304
     * @return string HTML TOC ready to display
3305
     */
3306
    public function get_teacher_toc_buttons()
3307
    {
3308
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3309
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3310
        $html = '';
3311
        if ($isAllow && $hideIcons == false) {
3312
            if ($this->get_lp_session_id() == api_get_session_id()) {
3313
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3314
                $html .= '<div class="btn-group">';
3315
                $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'>".
3316
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3317
                $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'>".
3318
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3319
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3320
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3321
                $html .= '</div>';
3322
                $html .= '</div>';
3323
            }
3324
        }
3325
3326
        return $html;
3327
    }
3328
3329
    /**
3330
     * Gets the learnpath maker name - generally the editor's name.
3331
     *
3332
     * @return string Learnpath maker name
3333
     */
3334
    public function get_maker()
3335
    {
3336
        if (!empty($this->maker)) {
3337
            return $this->maker;
3338
        }
3339
3340
        return '';
3341
    }
3342
3343
    /**
3344
     * Gets the learnpath name/title.
3345
     *
3346
     * @return string Learnpath name/title
3347
     */
3348
    public function get_name()
3349
    {
3350
        if (!empty($this->name)) {
3351
            return $this->name;
3352
        }
3353
3354
        return 'N/A';
3355
    }
3356
3357
    /**
3358
     * @return string
3359
     */
3360
    public function getNameNoTags()
3361
    {
3362
        return strip_tags($this->get_name());
3363
    }
3364
3365
    /**
3366
     * Gets a link to the resource from the present location, depending on item ID.
3367
     *
3368
     * @param string $type         Type of link expected
3369
     * @param int    $item_id      Learnpath item ID
3370
     * @param bool   $provided_toc
3371
     *
3372
     * @return string $provided_toc Link to the lp_item resource
3373
     */
3374
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
3375
    {
3376
        $course_id = $this->get_course_int_id();
3377
        $item_id = (int) $item_id;
3378
3379
        if (empty($item_id)) {
3380
            $item_id = $this->get_current_item_id();
3381
3382
            if (empty($item_id)) {
3383
                //still empty, this means there was no item_id given and we are not in an object context or
3384
                //the object property is empty, return empty link
3385
                $this->first();
3386
3387
                return '';
3388
            }
3389
        }
3390
3391
        $file = '';
3392
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3393
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3394
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3395
3396
        $sql = "SELECT
3397
                    l.lp_type as ltype,
3398
                    l.path as lpath,
3399
                    li.item_type as litype,
3400
                    li.path as lipath,
3401
                    li.parameters as liparams
3402
        		FROM $lp_table l
3403
                INNER JOIN $lp_item_table li
3404
                ON (li.lp_id = l.iid)
3405
        		WHERE
3406
        		    li.iid = $item_id
3407
        		";
3408
        $res = Database::query($sql);
3409
        if (Database::num_rows($res) > 0) {
3410
            $row = Database::fetch_array($res);
3411
            $lp_type = $row['ltype'];
3412
            $lp_path = $row['lpath'];
3413
            $lp_item_type = $row['litype'];
3414
            $lp_item_path = $row['lipath'];
3415
            $lp_item_params = $row['liparams'];
3416
3417
            if (empty($lp_item_params) && strpos($lp_item_path, '?') !== false) {
3418
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3419
            }
3420
            //$sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3421
            if ($type === 'http') {
3422
                //web path
3423
                //$course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3424
            } else {
3425
                //$course_path = $sys_course_path; //system path
3426
            }
3427
3428
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3429
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3430
            if (in_array(
3431
                $lp_item_type,
3432
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3433
            )
3434
            ) {
3435
                $lp_type = 1;
3436
            }
3437
3438
            // Now go through the specific cases to get the end of the path
3439
            // @todo Use constants instead of int values.
3440
3441
            switch ($lp_type) {
3442
                case 1:
3443
                    $file = self::rl_get_resource_link_for_learnpath(
3444
                        $course_id,
3445
                        $this->get_id(),
3446
                        $item_id,
3447
                        $this->get_view_id()
3448
                    );
3449
                    switch ($lp_item_type) {
3450
                        case 'document':
3451
                            // Shows a button to download the file instead of just downloading the file directly.
3452
                            $documentPathInfo = pathinfo($file);
3453
                            if (isset($documentPathInfo['extension'])) {
3454
                                $parsed = parse_url($documentPathInfo['extension']);
3455
                                if (isset($parsed['path'])) {
3456
                                    $extension = $parsed['path'];
3457
                                    $extensionsToDownload = [
3458
                                        'zip',
3459
                                        'ppt',
3460
                                        'pptx',
3461
                                        'ods',
3462
                                        'xlsx',
3463
                                        'xls',
3464
                                        'csv',
3465
                                        'doc',
3466
                                        'docx',
3467
                                        'dot',
3468
                                    ];
3469
3470
                                    if (in_array($extension, $extensionsToDownload)) {
3471
                                        $file = api_get_path(WEB_CODE_PATH).
3472
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
3473
                                    }
3474
                                }
3475
                            }
3476
                            break;
3477
                        case 'dir':
3478
                            $file = 'lp_content.php?type=dir';
3479
                            break;
3480
                        case 'link':
3481
                            if (Link::is_youtube_link($file)) {
3482
                                $src = Link::get_youtube_video_id($file);
3483
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3484
                            } elseif (Link::isVimeoLink($file)) {
3485
                                $src = Link::getVimeoLinkId($file);
3486
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3487
                            } else {
3488
                                // If the current site is HTTPS and the link is
3489
                                // HTTP, browsers will refuse opening the link
3490
                                $urlId = api_get_current_access_url_id();
3491
                                $url = api_get_access_url($urlId, false);
3492
                                $protocol = substr($url['url'], 0, 5);
3493
                                if ($protocol === 'https') {
3494
                                    $linkProtocol = substr($file, 0, 5);
3495
                                    if ($linkProtocol === 'http:') {
3496
                                        //this is the special intervention case
3497
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3498
                                    }
3499
                                }
3500
                            }
3501
                            break;
3502
                        case 'quiz':
3503
                            // Check how much attempts of a exercise exits in lp
3504
                            $lp_item_id = $this->get_current_item_id();
3505
                            $lp_view_id = $this->get_view_id();
3506
3507
                            $prevent_reinit = null;
3508
                            if (isset($this->items[$this->current])) {
3509
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3510
                            }
3511
3512
                            if (empty($provided_toc)) {
3513
                                if ($this->debug > 0) {
3514
                                    error_log('In learnpath::get_link() Loading get_toc ', 0);
3515
                                }
3516
                                $list = $this->get_toc();
3517
                            } else {
3518
                                if ($this->debug > 0) {
3519
                                    error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
3520
                                }
3521
                                $list = $provided_toc;
3522
                            }
3523
3524
                            $type_quiz = false;
3525
                            foreach ($list as $toc) {
3526
                                if ($toc['id'] == $lp_item_id && $toc['type'] == 'quiz') {
3527
                                    $type_quiz = true;
3528
                                }
3529
                            }
3530
3531
                            if ($type_quiz) {
3532
                                $lp_item_id = (int) $lp_item_id;
3533
                                $lp_view_id = (int) $lp_view_id;
3534
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3535
                                        WHERE
3536
                                            c_id = $course_id AND
3537
                                            lp_item_id='".$lp_item_id."' AND
3538
                                            lp_view_id ='".$lp_view_id."' AND
3539
                                            status='completed'";
3540
                                $result = Database::query($sql);
3541
                                $row_count = Database:: fetch_row($result);
3542
                                $count_item_view = (int) $row_count[0];
3543
                                $not_multiple_attempt = 0;
3544
                                if ($prevent_reinit === 1 && $count_item_view > 0) {
3545
                                    $not_multiple_attempt = 1;
3546
                                }
3547
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3548
                            }
3549
                            break;
3550
                    }
3551
3552
                    $tmp_array = explode('/', $file);
3553
                    $document_name = $tmp_array[count($tmp_array) - 1];
3554
                    if (strpos($document_name, '_DELETED_')) {
3555
                        $file = 'blank.php?error=document_deleted';
3556
                    }
3557
                    break;
3558
                case 2:
3559
                    if ($this->debug > 2) {
3560
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3561
                    }
3562
3563
                    if ($lp_item_type != 'dir') {
3564
                        // Quite complex here:
3565
                        // We want to make sure 'http://' (and similar) links can
3566
                        // be loaded as is (withouth the Chamilo path in front) but
3567
                        // some contents use this form: resource.htm?resource=http://blablabla
3568
                        // which means we have to find a protocol at the path's start, otherwise
3569
                        // it should not be considered as an external URL.
3570
                        // if ($this->prerequisites_match($item_id)) {
3571
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3572
                            if ($this->debug > 2) {
3573
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3574
                            }
3575
                            // Distant url, return as is.
3576
                            $file = $lp_item_path;
3577
                        } else {
3578
                            if ($this->debug > 2) {
3579
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3580
                            }
3581
                            // Prevent getting untranslatable urls.
3582
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3583
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3584
                            // Prepare the path.
3585
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3586
                            // TODO: Fix this for urls with protocol header.
3587
                            $file = str_replace('//', '/', $file);
3588
                            $file = str_replace(':/', '://', $file);
3589
                            if (substr($lp_path, -1) == '/') {
3590
                                $lp_path = substr($lp_path, 0, -1);
3591
                            }
3592
3593
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3594
                                // if file not found.
3595
                                $decoded = html_entity_decode($lp_item_path);
3596
                                list($decoded) = explode('?', $decoded);
3597
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3598
                                    $file = self::rl_get_resource_link_for_learnpath(
3599
                                        $course_id,
3600
                                        $this->get_id(),
3601
                                        $item_id,
3602
                                        $this->get_view_id()
3603
                                    );
3604
                                    if (empty($file)) {
3605
                                        $file = 'blank.php?error=document_not_found';
3606
                                    } else {
3607
                                        $tmp_array = explode('/', $file);
3608
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3609
                                        if (strpos($document_name, '_DELETED_')) {
3610
                                            $file = 'blank.php?error=document_deleted';
3611
                                        } else {
3612
                                            $file = 'blank.php?error=document_not_found';
3613
                                        }
3614
                                    }
3615
                                } else {
3616
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3617
                                }
3618
                            }
3619
                        }
3620
3621
                        // We want to use parameters if they were defined in the imsmanifest
3622
                        if (strpos($file, 'blank.php') === false) {
3623
                            $lp_item_params = ltrim($lp_item_params, '?');
3624
                            $file .= (strstr($file, '?') === false ? '?' : '').$lp_item_params;
3625
                        }
3626
                    } else {
3627
                        $file = 'lp_content.php?type=dir';
3628
                    }
3629
                    break;
3630
                case 3:
3631
                    if ($this->debug > 2) {
3632
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3633
                    }
3634
                    // Formatting AICC HACP append URL.
3635
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3636
                    if (!empty($lp_item_params)) {
3637
                        $aicc_append .= $lp_item_params.'&';
3638
                    }
3639
                    if ($lp_item_type != 'dir') {
3640
                        // Quite complex here:
3641
                        // We want to make sure 'http://' (and similar) links can
3642
                        // be loaded as is (withouth the Chamilo path in front) but
3643
                        // some contents use this form: resource.htm?resource=http://blablabla
3644
                        // which means we have to find a protocol at the path's start, otherwise
3645
                        // it should not be considered as an external URL.
3646
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3647
                            if ($this->debug > 2) {
3648
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3649
                            }
3650
                            // Distant url, return as is.
3651
                            $file = $lp_item_path;
3652
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3653
                            /*
3654
                            if (stristr($file,'<servername>') !== false) {
3655
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3656
                            }
3657
                            */
3658
                            if (stripos($file, '<servername>') !== false) {
3659
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3660
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3661
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3662
                            }
3663
3664
                            $file .= $aicc_append;
3665
                        } else {
3666
                            if ($this->debug > 2) {
3667
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3668
                            }
3669
                            // Prevent getting untranslatable urls.
3670
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3671
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3672
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3673
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3674
                            // TODO: Fix this for urls with protocol header.
3675
                            $file = str_replace('//', '/', $file);
3676
                            $file = str_replace(':/', '://', $file);
3677
                            $file .= $aicc_append;
3678
                        }
3679
                    } else {
3680
                        $file = 'lp_content.php?type=dir';
3681
                    }
3682
                    break;
3683
                case 4:
3684
                    break;
3685
                default:
3686
                    break;
3687
            }
3688
            // Replace &amp; by & because &amp; will break URL with params
3689
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3690
        }
3691
        if ($this->debug > 2) {
3692
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3693
        }
3694
3695
        return $file;
3696
    }
3697
3698
    /**
3699
     * Gets the latest usable view or generate a new one.
3700
     *
3701
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3702
     *
3703
     * @return int DB lp_view id
3704
     */
3705
    public function get_view($attempt_num = 0)
3706
    {
3707
        $search = '';
3708
        // Use $attempt_num to enable multi-views management (disabled so far).
3709
        if ($attempt_num != 0 && intval(strval($attempt_num)) == $attempt_num) {
3710
            $search = 'AND view_count = '.$attempt_num;
3711
        }
3712
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3713
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3714
3715
        $course_id = api_get_course_int_id();
3716
        $sessionId = api_get_session_id();
3717
3718
        $sql = "SELECT iid, view_count FROM $lp_view_table
3719
        		WHERE
3720
        		    c_id = $course_id AND
3721
        		    lp_id = ".$this->get_id()." AND
3722
        		    user_id = ".$this->get_user_id()." AND
3723
        		    session_id = $sessionId
3724
        		    $search
3725
                ORDER BY view_count DESC";
3726
        $res = Database::query($sql);
3727
        if (Database::num_rows($res) > 0) {
3728
            $row = Database::fetch_array($res);
3729
            $this->lp_view_id = $row['iid'];
3730
        } elseif (!api_is_invitee()) {
3731
            // There is no database record, create one.
3732
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3733
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3734
            Database::query($sql);
3735
            $id = Database::insert_id();
3736
            $this->lp_view_id = $id;
3737
3738
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
3739
            Database::query($sql);
3740
        }
3741
3742
        return $this->lp_view_id;
3743
    }
3744
3745
    /**
3746
     * Gets the current view id.
3747
     *
3748
     * @return int View ID (from lp_view)
3749
     */
3750
    public function get_view_id()
3751
    {
3752
        if (!empty($this->lp_view_id)) {
3753
            return (int) $this->lp_view_id;
3754
        }
3755
3756
        return 0;
3757
    }
3758
3759
    /**
3760
     * Gets the update queue.
3761
     *
3762
     * @return array Array containing IDs of items to be updated by JavaScript
3763
     */
3764
    public function get_update_queue()
3765
    {
3766
        return $this->update_queue;
3767
    }
3768
3769
    /**
3770
     * Gets the user ID.
3771
     *
3772
     * @return int User ID
3773
     */
3774
    public function get_user_id()
3775
    {
3776
        if (!empty($this->user_id)) {
3777
            return (int) $this->user_id;
3778
        }
3779
3780
        return false;
3781
    }
3782
3783
    /**
3784
     * Checks if any of the items has an audio element attached.
3785
     *
3786
     * @return bool True or false
3787
     */
3788
    public function has_audio()
3789
    {
3790
        $has = false;
3791
        foreach ($this->items as $i => $item) {
3792
            if (!empty($this->items[$i]->audio)) {
3793
                $has = true;
3794
                break;
3795
            }
3796
        }
3797
3798
        return $has;
3799
    }
3800
3801
    /**
3802
     * Moves an item up and down at its level.
3803
     *
3804
     * @param int    $id        Item to move up and down
3805
     * @param string $direction Direction 'up' or 'down'
3806
     *
3807
     * @return bool|int
3808
     */
3809
    public function move_item($id, $direction)
3810
    {
3811
        $course_id = api_get_course_int_id();
3812
        if (empty($id) || empty($direction)) {
3813
            return false;
3814
        }
3815
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3816
        $sql_sel = "SELECT *
3817
                    FROM $tbl_lp_item
3818
                    WHERE
3819
                        iid = $id
3820
                    ";
3821
        $res_sel = Database::query($sql_sel);
3822
        // Check if elem exists.
3823
        if (Database::num_rows($res_sel) < 1) {
3824
            return false;
3825
        }
3826
        // Gather data.
3827
        $row = Database::fetch_array($res_sel);
3828
        $previous = $row['previous_item_id'];
3829
        $next = $row['next_item_id'];
3830
        $display = $row['display_order'];
3831
        $parent = $row['parent_item_id'];
3832
        $lp = $row['lp_id'];
3833
        // Update the item (switch with previous/next one).
3834
        switch ($direction) {
3835
            case 'up':
3836
                if ($display > 1) {
3837
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3838
                                 WHERE iid = $previous";
3839
                    $res_sel2 = Database::query($sql_sel2);
3840
                    if (Database::num_rows($res_sel2) < 1) {
3841
                        $previous_previous = 0;
3842
                    }
3843
                    // Gather data.
3844
                    $row2 = Database::fetch_array($res_sel2);
3845
                    $previous_previous = $row2['previous_item_id'];
3846
                    // Update previous_previous item (switch "next" with current).
3847
                    if ($previous_previous != 0) {
3848
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3849
                                        next_item_id = $id
3850
                                    WHERE iid = $previous_previous";
3851
                        Database::query($sql_upd2);
3852
                    }
3853
                    // Update previous item (switch with current).
3854
                    if ($previous != 0) {
3855
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3856
                                    next_item_id = $next,
3857
                                    previous_item_id = $id,
3858
                                    display_order = display_order +1
3859
                                    WHERE iid = $previous";
3860
                        Database::query($sql_upd2);
3861
                    }
3862
3863
                    // Update current item (switch with previous).
3864
                    if ($id != 0) {
3865
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3866
                                        next_item_id = $previous,
3867
                                        previous_item_id = $previous_previous,
3868
                                        display_order = display_order-1
3869
                                    WHERE c_id = ".$course_id." AND id = $id";
3870
                        Database::query($sql_upd2);
3871
                    }
3872
                    // Update next item (new previous item).
3873
                    if (!empty($next)) {
3874
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
3875
                                     WHERE iid = $next";
3876
                        Database::query($sql_upd2);
3877
                    }
3878
                    $display = $display - 1;
3879
                }
3880
                break;
3881
            case 'down':
3882
                if ($next != 0) {
3883
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3884
                                 WHERE iid = $next";
3885
                    $res_sel2 = Database::query($sql_sel2);
3886
                    if (Database::num_rows($res_sel2) < 1) {
3887
                        $next_next = 0;
3888
                    }
3889
                    // Gather data.
3890
                    $row2 = Database::fetch_array($res_sel2);
3891
                    $next_next = $row2['next_item_id'];
3892
                    // Update previous item (switch with current).
3893
                    if ($previous != 0) {
3894
                        $sql_upd2 = "UPDATE $tbl_lp_item
3895
                                     SET next_item_id = $next
3896
                                     WHERE iid = $previous";
3897
                        Database::query($sql_upd2);
3898
                    }
3899
                    // Update current item (switch with previous).
3900
                    if ($id != 0) {
3901
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3902
                                     previous_item_id = $next,
3903
                                     next_item_id = $next_next,
3904
                                     display_order = display_order + 1
3905
                                     WHERE iid = $id";
3906
                        Database::query($sql_upd2);
3907
                    }
3908
3909
                    // Update next item (new previous item).
3910
                    if ($next != 0) {
3911
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3912
                                     previous_item_id = $previous,
3913
                                     next_item_id = $id,
3914
                                     display_order = display_order-1
3915
                                     WHERE iid = $next";
3916
                        Database::query($sql_upd2);
3917
                    }
3918
3919
                    // Update next_next item (switch "previous" with current).
3920
                    if ($next_next != 0) {
3921
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3922
                                     previous_item_id = $id
3923
                                     WHERE iid = $next_next";
3924
                        Database::query($sql_upd2);
3925
                    }
3926
                    $display = $display + 1;
3927
                }
3928
                break;
3929
            default:
3930
                return false;
3931
        }
3932
3933
        return $display;
3934
    }
3935
3936
    /**
3937
     * Move a LP up (display_order).
3938
     *
3939
     * @param int $lp_id      Learnpath ID
3940
     * @param int $categoryId Category ID
3941
     *
3942
     * @return bool
3943
     */
3944
    public static function move_up($lp_id, $categoryId = 0)
3945
    {
3946
        $courseId = api_get_course_int_id();
3947
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3948
3949
        $categoryCondition = '';
3950
        if (!empty($categoryId)) {
3951
            $categoryId = (int) $categoryId;
3952
            $categoryCondition = " AND category_id = $categoryId";
3953
        }
3954
        $sql = "SELECT * FROM $lp_table
3955
                WHERE c_id = $courseId
3956
                $categoryCondition
3957
                ORDER BY display_order";
3958
        $res = Database::query($sql);
3959
        if ($res === false) {
3960
            return false;
3961
        }
3962
3963
        $lps = [];
3964
        $lp_order = [];
3965
        $num = Database::num_rows($res);
3966
        // First check the order is correct, globally (might be wrong because
3967
        // of versions < 1.8.4)
3968
        if ($num > 0) {
3969
            $i = 1;
3970
            while ($row = Database::fetch_array($res)) {
3971
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
3972
                    $sql = "UPDATE $lp_table SET display_order = $i
3973
                            WHERE iid = ".$row['iid'];
3974
                    Database::query($sql);
3975
                }
3976
                $row['display_order'] = $i;
3977
                $lps[$row['iid']] = $row;
3978
                $lp_order[$i] = $row['iid'];
3979
                $i++;
3980
            }
3981
        }
3982
        if ($num > 1) { // If there's only one element, no need to sort.
3983
            $order = $lps[$lp_id]['display_order'];
3984
            if ($order > 1) { // If it's the first element, no need to move up.
3985
                $sql = "UPDATE $lp_table SET display_order = $order
3986
                        WHERE iid = ".$lp_order[$order - 1];
3987
                Database::query($sql);
3988
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
3989
                        WHERE iid = $lp_id";
3990
                Database::query($sql);
3991
            }
3992
        }
3993
3994
        return true;
3995
    }
3996
3997
    /**
3998
     * Move a learnpath down (display_order).
3999
     *
4000
     * @param int $lp_id      Learnpath ID
4001
     * @param int $categoryId Category ID
4002
     *
4003
     * @return bool
4004
     */
4005
    public static function move_down($lp_id, $categoryId = 0)
4006
    {
4007
        $courseId = api_get_course_int_id();
4008
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4009
4010
        $categoryCondition = '';
4011
        if (!empty($categoryId)) {
4012
            $categoryId = (int) $categoryId;
4013
            $categoryCondition = " AND category_id = $categoryId";
4014
        }
4015
4016
        $sql = "SELECT * FROM $lp_table
4017
                WHERE c_id = $courseId
4018
                $categoryCondition
4019
                ORDER BY display_order";
4020
        $res = Database::query($sql);
4021
        if ($res === false) {
4022
            return false;
4023
        }
4024
        $lps = [];
4025
        $lp_order = [];
4026
        $num = Database::num_rows($res);
4027
        $max = 0;
4028
        // First check the order is correct, globally (might be wrong because
4029
        // of versions < 1.8.4).
4030
        if ($num > 0) {
4031
            $i = 1;
4032
            while ($row = Database::fetch_array($res)) {
4033
                $max = $i;
4034
                if ($row['display_order'] != $i) {
4035
                    // If we find a gap in the order, we need to fix it.
4036
                    $sql = "UPDATE $lp_table SET display_order = $i
4037
                              WHERE iid = ".$row['iid'];
4038
                    Database::query($sql);
4039
                }
4040
                $row['display_order'] = $i;
4041
                $lps[$row['iid']] = $row;
4042
                $lp_order[$i] = $row['iid'];
4043
                $i++;
4044
            }
4045
        }
4046
        if ($num > 1) { // If there's only one element, no need to sort.
4047
            $order = $lps[$lp_id]['display_order'];
4048
            if ($order < $max) { // If it's the first element, no need to move up.
4049
                $sql = "UPDATE $lp_table SET display_order = $order
4050
                        WHERE iid = ".$lp_order[$order + 1];
4051
                Database::query($sql);
4052
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4053
                        WHERE iid = $lp_id";
4054
                Database::query($sql);
4055
            }
4056
        }
4057
4058
        return true;
4059
    }
4060
4061
    /**
4062
     * Updates learnpath attributes to point to the next element
4063
     * The last part is similar to set_current_item but processing the other way around.
4064
     */
4065
    public function next()
4066
    {
4067
        if ($this->debug > 0) {
4068
            error_log('In learnpath::next()', 0);
4069
        }
4070
        $this->last = $this->get_current_item_id();
4071
        $this->items[$this->last]->save(
4072
            false,
4073
            $this->prerequisites_match($this->last)
4074
        );
4075
        $this->autocomplete_parents($this->last);
4076
        $new_index = $this->get_next_index();
4077
        if ($this->debug > 2) {
4078
            error_log('New index: '.$new_index, 0);
4079
        }
4080
        $this->index = $new_index;
4081
        if ($this->debug > 2) {
4082
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4083
        }
4084
        $this->current = $this->ordered_items[$new_index];
4085
        if ($this->debug > 2) {
4086
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4087
        }
4088
    }
4089
4090
    /**
4091
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4092
     * class, this might be redefined to allow several behaviours depending on the document type.
4093
     *
4094
     * @param int $id Resource ID
4095
     */
4096
    public function open($id)
4097
    {
4098
        // TODO:
4099
        // set the current resource attribute to this resource
4100
        // switch on element type (redefine in child class?)
4101
        // set status for this item to "opened"
4102
        // start timer
4103
        // initialise score
4104
        $this->index = 0; //or = the last item seen (see $this->last)
4105
    }
4106
4107
    /**
4108
     * Check that all prerequisites are fulfilled. Returns true and an
4109
     * empty string on success, returns false
4110
     * and the prerequisite string on error.
4111
     * This function is based on the rules for aicc_script language as
4112
     * described in the SCORM 1.2 CAM documentation page 108.
4113
     *
4114
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4115
     *
4116
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4117
     *              string otherwise
4118
     */
4119
    public function prerequisites_match($itemId = null)
4120
    {
4121
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4122
        if ($allow) {
4123
            if (api_is_allowed_to_edit() ||
4124
                api_is_platform_admin(true) ||
4125
                api_is_drh() ||
4126
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4127
            ) {
4128
                return true;
4129
            }
4130
        }
4131
4132
        $debug = $this->debug;
4133
        if ($debug > 0) {
4134
            error_log('In learnpath::prerequisites_match()');
4135
        }
4136
4137
        if (empty($itemId)) {
4138
            $itemId = $this->current;
4139
        }
4140
4141
        $currentItem = $this->getItem($itemId);
4142
4143
        if ($currentItem) {
4144
            if ($this->type == 2) {
4145
                // Getting prereq from scorm
4146
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4147
            } else {
4148
                $prereq_string = $currentItem->get_prereq_string();
4149
            }
4150
4151
            if (empty($prereq_string)) {
4152
                if ($debug > 0) {
4153
                    error_log('Found prereq_string is empty return true');
4154
                }
4155
4156
                return true;
4157
            }
4158
4159
            // Clean spaces.
4160
            $prereq_string = str_replace(' ', '', $prereq_string);
4161
            if ($debug > 0) {
4162
                error_log('Found prereq_string: '.$prereq_string, 0);
4163
            }
4164
4165
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4166
            $result = $currentItem->parse_prereq(
4167
                $prereq_string,
4168
                $this->items,
4169
                $this->refs_list,
4170
                $this->get_user_id()
4171
            );
4172
4173
            if ($result === false) {
4174
                $this->set_error_msg($currentItem->prereq_alert);
4175
            }
4176
        } else {
4177
            $result = true;
4178
            if ($debug > 1) {
4179
                error_log('$this->items['.$itemId.'] was not an object', 0);
4180
            }
4181
        }
4182
4183
        if ($debug > 1) {
4184
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4185
        }
4186
4187
        return $result;
4188
    }
4189
4190
    /**
4191
     * Updates learnpath attributes to point to the previous element
4192
     * The last part is similar to set_current_item but processing the other way around.
4193
     */
4194
    public function previous()
4195
    {
4196
        $this->last = $this->get_current_item_id();
4197
        $this->items[$this->last]->save(
4198
            false,
4199
            $this->prerequisites_match($this->last)
4200
        );
4201
        $this->autocomplete_parents($this->last);
4202
        $new_index = $this->get_previous_index();
4203
        $this->index = $new_index;
4204
        $this->current = $this->ordered_items[$new_index];
4205
    }
4206
4207
    /**
4208
     * Publishes a learnpath. This basically means show or hide the learnpath
4209
     * to normal users.
4210
     * Can be used as abstract.
4211
     *
4212
     * @param int $lp_id          Learnpath ID
4213
     * @param int $set_visibility New visibility
4214
     *
4215
     * @return bool
4216
     */
4217
    public static function toggle_visibility($lp_id, $set_visibility = 1)
4218
    {
4219
        $action = 'visible';
4220
        if ($set_visibility != 1) {
4221
            $action = 'invisible';
4222
            self::toggle_publish($lp_id, 'i');
4223
        }
4224
4225
        return api_item_property_update(
4226
            api_get_course_info(),
4227
            TOOL_LEARNPATH,
4228
            $lp_id,
4229
            $action,
4230
            api_get_user_id()
4231
        );
4232
    }
4233
4234
    /**
4235
     * Publishes a learnpath category.
4236
     * This basically means show or hide the learnpath category to normal users.
4237
     *
4238
     * @param int $id
4239
     * @param int $visibility
4240
     *
4241
     * @throws \Doctrine\ORM\NonUniqueResultException
4242
     * @throws \Doctrine\ORM\ORMException
4243
     * @throws \Doctrine\ORM\OptimisticLockException
4244
     * @throws \Doctrine\ORM\TransactionRequiredException
4245
     *
4246
     * @return bool
4247
     */
4248
    public static function toggleCategoryVisibility($id, $visibility = 1)
4249
    {
4250
        $action = 'visible';
4251
        if ($visibility != 1) {
4252
            self::toggleCategoryPublish($id, 0);
4253
            $action = 'invisible';
4254
        }
4255
4256
        return api_item_property_update(
4257
            api_get_course_info(),
4258
            TOOL_LEARNPATH_CATEGORY,
4259
            $id,
4260
            $action,
4261
            api_get_user_id()
4262
        );
4263
    }
4264
4265
    /**
4266
     * Publishes a learnpath. This basically means show or hide the learnpath
4267
     * on the course homepage
4268
     * Can be used as abstract.
4269
     *
4270
     * @param int    $lp_id          Learnpath id
4271
     * @param string $set_visibility New visibility (v/i - visible/invisible)
4272
     *
4273
     * @return bool
4274
     */
4275
    public static function toggle_publish($id, $setVisibility = 'v')
4276
    {
4277
        $addShortcut = false;
4278
        if ($setVisibility === 'v') {
4279
            $addShortcut = true;
4280
        }
4281
        $repo = Container::getLpRepository();
4282
        /** @var CLp $lp */
4283
        $lp = $repo->find($id);
4284
        $repoShortcut = Container::getShortcutRepository();
4285
        if ($addShortcut) {
4286
            $shortcut = new CShortcut();
4287
            $shortcut->setName($lp->getName());
4288
            $shortcut->setShortCutNode($lp->getResourceNode());
4289
4290
            $courseEntity = api_get_course_entity(api_get_course_int_id());
4291
            $repoShortcut->addResourceNode($shortcut, api_get_user_entity(api_get_user_id()), $courseEntity);
4292
            $repoShortcut->getEntityManager()->flush();
4293
        } else {
4294
            $shortcut = $repoShortcut->getShortcutFromResource($lp);
4295
            if (null !== $shortcut) {
4296
                $repoShortcut->getEntityManager()->remove($shortcut);
4297
                $repoShortcut->getEntityManager()->flush();
4298
            }
4299
        }
4300
4301
        return true;
4302
        /*
4303
        $course_id = api_get_course_int_id();
4304
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4305
        $lp_id = (int) $lp_id;
4306
        $sql = "SELECT * FROM $tbl_lp
4307
                WHERE iid = $lp_id";
4308
        $result = Database::query($sql);
4309
4310
        if (Database::num_rows($result)) {
4311
            $row = Database::fetch_array($result);
4312
            $name = Database::escape_string($row['name']);
4313
            if ($set_visibility == 'i') {
4314
                $v = 0;
4315
            }
4316
            if ($set_visibility == 'v') {
4317
                $v = 1;
4318
            }
4319
4320
            $session_id = api_get_session_id();
4321
            $session_condition = api_get_session_condition($session_id);
4322
4323
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4324
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4325
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4326
4327
            $sql = "SELECT * FROM $tbl_tool
4328
                    WHERE
4329
                        c_id = $course_id AND
4330
                        (link = '$link' OR link = '$oldLink') AND
4331
                        image = 'scormbuilder.gif' AND
4332
                        (
4333
                            link LIKE '$link%' OR
4334
                            link LIKE '$oldLink%'
4335
                        )
4336
                        $session_condition
4337
                    ";
4338
4339
            $result = Database::query($sql);
4340
            $num = Database::num_rows($result);
4341
            if ($set_visibility == 'i' && $num > 0) {
4342
                $sql = "DELETE FROM $tbl_tool
4343
                        WHERE
4344
                            c_id = $course_id AND
4345
                            (link = '$link' OR link = '$oldLink') AND
4346
                            image='scormbuilder.gif'
4347
                            $session_condition";
4348
                Database::query($sql);
4349
            } elseif ($set_visibility == 'v' && $num == 0) {
4350
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4351
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4352
                Database::query($sql);
4353
                $insertId = Database::insert_id();
4354
                if ($insertId) {
4355
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4356
                    Database::query($sql);
4357
                }
4358
            } elseif ($set_visibility == 'v' && $num > 0) {
4359
                $sql = "UPDATE $tbl_tool SET
4360
                            c_id = $course_id,
4361
                            name = '$name',
4362
                            link = '$link',
4363
                            image = 'scormbuilder.gif',
4364
                            visibility = '$v',
4365
                            admin = '0',
4366
                            address = 'pastillegris.gif',
4367
                            added_tool = 0,
4368
                            session_id = $session_id
4369
                        WHERE
4370
                            c_id = ".$course_id." AND
4371
                            (link = '$link' OR link = '$oldLink') AND
4372
                            image='scormbuilder.gif'
4373
                            $session_condition
4374
                        ";
4375
                Database::query($sql);
4376
            } else {
4377
                // Parameter and database incompatible, do nothing, exit.
4378
                return false;
4379
            }
4380
        } else {
4381
            return false;
4382
        }*/
4383
    }
4384
4385
    /**
4386
     * Publishes a learnpath.
4387
     * Show or hide the learnpath category on the course homepage.
4388
     *
4389
     * @param int $id
4390
     * @param int $setVisibility
4391
     *
4392
     * @throws \Doctrine\ORM\NonUniqueResultException
4393
     * @throws \Doctrine\ORM\ORMException
4394
     * @throws \Doctrine\ORM\OptimisticLockException
4395
     * @throws \Doctrine\ORM\TransactionRequiredException
4396
     *
4397
     * @return bool
4398
     */
4399
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4400
    {
4401
        $courseId = api_get_course_int_id();
4402
        $sessionId = api_get_session_id();
4403
        $sessionCondition = api_get_session_condition(
4404
            $sessionId,
4405
            true,
4406
            false,
4407
            't.sessionId'
4408
        );
4409
4410
        $em = Database::getManager();
4411
4412
        /** @var CLpCategory $category */
4413
        $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
4414
4415
        if (!$category) {
4416
            return false;
4417
        }
4418
4419
        if (empty($courseId)) {
4420
            return false;
4421
        }
4422
4423
        $link = self::getCategoryLinkForTool($id);
4424
4425
        /** @var CTool $tool */
4426
        $tool = $em->createQuery("
4427
                SELECT t FROM ChamiloCourseBundle:CTool t
4428
                WHERE
4429
                    t.course = :course AND
4430
                    t.link = :link1 AND
4431
                    t.image LIKE 'lp_category.%' AND
4432
                    t.link LIKE :link2
4433
                    $sessionCondition
4434
            ")
4435
            ->setParameters([
4436
                'course' => $courseId,
4437
                'link1' => $link,
4438
                'link2' => "$link%",
4439
            ])
4440
            ->getOneOrNullResult();
4441
4442
        if ($setVisibility == 0 && $tool) {
4443
            $em->remove($tool);
4444
            $em->flush();
4445
4446
            return true;
4447
        }
4448
4449
        if ($setVisibility == 1 && !$tool) {
4450
            $tool = new CTool();
4451
            $tool
4452
                ->setCategory('authoring')
4453
                ->setCourse(api_get_course_entity($courseId))
4454
                ->setName(strip_tags($category->getName()))
4455
                ->setLink($link)
4456
                ->setImage('lp_category.png')
4457
                ->setVisibility(1)
4458
                ->setAdmin(0)
4459
                ->setAddress('pastillegris.gif')
4460
                ->setAddedTool(0)
4461
                ->setSessionId($sessionId)
4462
                ->setTarget('_self');
4463
4464
            $em->persist($tool);
4465
            $em->flush();
4466
4467
            $tool->setId($tool->getIid());
4468
4469
            $em->persist($tool);
4470
            $em->flush();
4471
4472
            return true;
4473
        }
4474
4475
        if ($setVisibility == 1 && $tool) {
4476
            $tool
4477
                ->setName(strip_tags($category->getName()))
4478
                ->setVisibility(1);
4479
4480
            $em->persist($tool);
4481
            $em->flush();
4482
4483
            return true;
4484
        }
4485
4486
        return false;
4487
    }
4488
4489
    /**
4490
     * Check if the learnpath category is visible for a user.
4491
     *
4492
     * @param int
4493
     * @param int
4494
     *
4495
     * @return bool
4496
     */
4497
    public static function categoryIsVisibleForStudent(
4498
        CLpCategory $category,
4499
        User $user,
4500
        $courseId = 0,
4501
        $sessionId = 0
4502
    ) {
4503
        if (empty($category)) {
4504
            return false;
4505
        }
4506
4507
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4508
4509
        if ($isAllowedToEdit) {
4510
            return true;
4511
        }
4512
4513
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4514
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4515
4516
        $courseInfo = api_get_course_info_by_id($courseId);
4517
4518
        $categoryVisibility = api_get_item_visibility(
4519
            $courseInfo,
4520
            TOOL_LEARNPATH_CATEGORY,
4521
            $category->getId(),
4522
            $sessionId
4523
        );
4524
4525
        if ($categoryVisibility !== 1 && $categoryVisibility != -1) {
4526
            return false;
4527
        }
4528
4529
        $subscriptionSettings = self::getSubscriptionSettings();
4530
4531
        if ($subscriptionSettings['allow_add_users_to_lp_category'] == false) {
4532
            return true;
4533
        }
4534
4535
        $users = $category->getUsers();
4536
4537
        if (empty($users) || !$users->count()) {
4538
            return true;
4539
        }
4540
4541
        if ($category->hasUserAdded($user)) {
4542
            return true;
4543
        }
4544
4545
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4546
        if (!empty($groups)) {
4547
            $em = Database::getManager();
4548
4549
            /** @var ItemPropertyRepository $itemRepo */
4550
            $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4551
4552
            /** @var CourseRepository $courseRepo */
4553
            $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4554
            $session = null;
4555
            if (!empty($sessionId)) {
4556
                $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4557
            }
4558
4559
            if ($courseId != 0) {
4560
                $course = $courseRepo->find($courseId);
4561
4562
                // Subscribed groups to a LP
4563
                $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4564
                    TOOL_LEARNPATH_CATEGORY,
4565
                    $category->getId(),
4566
                    $course,
4567
                    $session
4568
                );
4569
            }
4570
4571
            if (!empty($subscribedGroupsInLp)) {
4572
                $groups = array_column($groups, 'iid');
4573
                /** @var CItemProperty $item */
4574
                foreach ($subscribedGroupsInLp as $item) {
4575
                    if ($item->getGroup() &&
4576
                        in_array($item->getGroup()->getId(), $groups)
4577
                    ) {
4578
                        return true;
4579
                    }
4580
                }
4581
            }
4582
        }
4583
4584
        return false;
4585
    }
4586
4587
    /**
4588
     * Check if a learnpath category is published as course tool.
4589
     *
4590
     * @param int $courseId
4591
     *
4592
     * @return bool
4593
     */
4594
    public static function categoryIsPublished(CLpCategory $category, $courseId)
4595
    {
4596
        return false;
4597
        $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...
4598
        $em = Database::getManager();
4599
4600
        $tools = $em
4601
            ->createQuery("
4602
                SELECT t FROM ChamiloCourseBundle:CTool t
4603
                WHERE t.course = :course AND
4604
                    t.name = :name AND
4605
                    t.image LIKE 'lp_category.%' AND
4606
                    t.link LIKE :link
4607
            ")
4608
            ->setParameters([
4609
                'course' => $courseId,
4610
                'name' => strip_tags($category->getName()),
4611
                'link' => "$link%",
4612
            ])
4613
            ->getResult();
4614
4615
        /** @var CTool $tool */
4616
        $tool = current($tools);
4617
4618
        return $tool ? $tool->getVisibility() : false;
4619
    }
4620
4621
    /**
4622
     * Restart the whole learnpath. Return the URL of the first element.
4623
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4624
     * To use a similar method  statically, use the create_new_attempt() method.
4625
     *
4626
     * @return bool
4627
     */
4628
    public function restart()
4629
    {
4630
        if ($this->debug > 0) {
4631
            error_log('In learnpath::restart()', 0);
4632
        }
4633
        // TODO
4634
        // Call autosave method to save the current progress.
4635
        //$this->index = 0;
4636
        if (api_is_invitee()) {
4637
            return false;
4638
        }
4639
        $session_id = api_get_session_id();
4640
        $course_id = api_get_course_int_id();
4641
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4642
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4643
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4644
        if ($this->debug > 2) {
4645
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4646
        }
4647
        Database::query($sql);
4648
        $view_id = Database::insert_id();
4649
4650
        if ($view_id) {
4651
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4652
            Database::query($sql);
4653
            $this->lp_view_id = $view_id;
4654
            $this->attempt = $this->attempt + 1;
4655
        } else {
4656
            $this->error = 'Could not insert into item_view table...';
4657
4658
            return false;
4659
        }
4660
        $this->autocomplete_parents($this->current);
4661
        foreach ($this->items as $index => $dummy) {
4662
            $this->items[$index]->restart();
4663
            $this->items[$index]->set_lp_view($this->lp_view_id);
4664
        }
4665
        $this->first();
4666
4667
        return true;
4668
    }
4669
4670
    /**
4671
     * Saves the current item.
4672
     *
4673
     * @return bool
4674
     */
4675
    public function save_current()
4676
    {
4677
        $debug = $this->debug;
4678
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4679
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4680
        if ($debug) {
4681
            error_log('save_current() saving item '.$this->current, 0);
4682
            error_log(''.print_r($this->items, true), 0);
4683
        }
4684
        if (isset($this->items[$this->current]) &&
4685
            is_object($this->items[$this->current])
4686
        ) {
4687
            if ($debug) {
4688
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4689
            }
4690
4691
            $res = $this->items[$this->current]->save(
4692
                false,
4693
                $this->prerequisites_match($this->current)
4694
            );
4695
            $this->autocomplete_parents($this->current);
4696
            $status = $this->items[$this->current]->get_status();
4697
            $this->update_queue[$this->current] = $status;
4698
4699
            if ($debug) {
4700
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4701
            }
4702
4703
            return $res;
4704
        }
4705
4706
        return false;
4707
    }
4708
4709
    /**
4710
     * Saves the given item.
4711
     *
4712
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4713
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4714
     *
4715
     * @return bool
4716
     */
4717
    public function save_item($item_id = null, $from_outside = true)
4718
    {
4719
        $debug = $this->debug;
4720
        if ($debug) {
4721
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4722
        }
4723
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4724
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4725
        if (empty($item_id)) {
4726
            $item_id = (int) $_REQUEST['id'];
4727
        }
4728
4729
        if (empty($item_id)) {
4730
            $item_id = $this->get_current_item_id();
4731
        }
4732
        if (isset($this->items[$item_id]) &&
4733
            is_object($this->items[$item_id])
4734
        ) {
4735
            if ($debug) {
4736
                error_log('Object exists');
4737
            }
4738
4739
            // Saving the item.
4740
            $res = $this->items[$item_id]->save(
4741
                $from_outside,
4742
                $this->prerequisites_match($item_id)
4743
            );
4744
4745
            if ($debug) {
4746
                error_log('update_queue before:');
4747
                error_log(print_r($this->update_queue, 1));
4748
            }
4749
            $this->autocomplete_parents($item_id);
4750
4751
            $status = $this->items[$item_id]->get_status();
4752
            $this->update_queue[$item_id] = $status;
4753
4754
            if ($debug) {
4755
                error_log('get_status(): '.$status);
4756
                error_log('update_queue after:');
4757
                error_log(print_r($this->update_queue, 1));
4758
            }
4759
4760
            return $res;
4761
        }
4762
4763
        return false;
4764
    }
4765
4766
    /**
4767
     * Saves the last item seen's ID only in case.
4768
     */
4769
    public function save_last()
4770
    {
4771
        $course_id = api_get_course_int_id();
4772
        $debug = $this->debug;
4773
        if ($debug) {
4774
            error_log('In learnpath::save_last()', 0);
4775
        }
4776
        $session_condition = api_get_session_condition(
4777
            api_get_session_id(),
4778
            true,
4779
            false
4780
        );
4781
        $table = Database::get_course_table(TABLE_LP_VIEW);
4782
4783
        if (isset($this->current) && !api_is_invitee()) {
4784
            if ($debug) {
4785
                error_log('Saving current item ('.$this->current.') for later review', 0);
4786
            }
4787
            $sql = "UPDATE $table SET
4788
                        last_item = ".$this->get_current_item_id()."
4789
                    WHERE
4790
                        c_id = $course_id AND
4791
                        lp_id = ".$this->get_id()." AND
4792
                        user_id = ".$this->get_user_id()." ".$session_condition;
4793
4794
            if ($debug) {
4795
                error_log('Saving last item seen : '.$sql, 0);
4796
            }
4797
            Database::query($sql);
4798
        }
4799
4800
        if (!api_is_invitee()) {
4801
            // Save progress.
4802
            list($progress) = $this->get_progress_bar_text('%');
4803
            if ($progress >= 0 && $progress <= 100) {
4804
                $progress = (int) $progress;
4805
                $sql = "UPDATE $table SET
4806
                            progress = $progress
4807
                        WHERE
4808
                            c_id = $course_id AND
4809
                            lp_id = ".$this->get_id()." AND
4810
                            user_id = ".$this->get_user_id()." ".$session_condition;
4811
                // Ignore errors as some tables might not have the progress field just yet.
4812
                Database::query($sql);
4813
                $this->progress_db = $progress;
4814
            }
4815
        }
4816
    }
4817
4818
    /**
4819
     * Sets the current item ID (checks if valid and authorized first).
4820
     *
4821
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
4822
     */
4823
    public function set_current_item($item_id = null)
4824
    {
4825
        $debug = $this->debug;
4826
        if ($debug) {
4827
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
4828
        }
4829
        if (empty($item_id)) {
4830
            if ($debug) {
4831
                error_log('No new current item given, ignore...', 0);
4832
            }
4833
            // Do nothing.
4834
        } else {
4835
            if ($debug) {
4836
                error_log('New current item given is '.$item_id.'...', 0);
4837
            }
4838
            if (is_numeric($item_id)) {
4839
                $item_id = (int) $item_id;
4840
                // TODO: Check in database here.
4841
                $this->last = $this->current;
4842
                $this->current = $item_id;
4843
                // TODO: Update $this->index as well.
4844
                foreach ($this->ordered_items as $index => $item) {
4845
                    if ($item == $this->current) {
4846
                        $this->index = $index;
4847
                        break;
4848
                    }
4849
                }
4850
                if ($debug) {
4851
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
4852
                }
4853
            } else {
4854
                if ($debug) {
4855
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
4856
                }
4857
            }
4858
        }
4859
    }
4860
4861
    /**
4862
     * Sets the encoding.
4863
     *
4864
     * @param string $enc New encoding
4865
     *
4866
     * @return bool
4867
     *
4868
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
4869
     */
4870
    public function set_encoding($enc = 'UTF-8')
4871
    {
4872
        $enc = api_refine_encoding_id($enc);
4873
        if (empty($enc)) {
4874
            $enc = api_get_system_encoding();
4875
        }
4876
        if (api_is_encoding_supported($enc)) {
4877
            $lp = $this->get_id();
4878
            if ($lp != 0) {
4879
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4880
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
4881
                        WHERE iid = ".$lp;
4882
                $res = Database::query($sql);
4883
4884
                return $res;
4885
            }
4886
        }
4887
4888
        return false;
4889
    }
4890
4891
    /**
4892
     * Sets the JS lib setting in the database directly.
4893
     * This is the JavaScript library file this lp needs to load on startup.
4894
     *
4895
     * @param string $lib Proximity setting
4896
     *
4897
     * @return bool True on update success. False otherwise.
4898
     */
4899
    public function set_jslib($lib = '')
4900
    {
4901
        $lp = $this->get_id();
4902
4903
        if ($lp != 0) {
4904
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4905
            $lib = Database::escape_string($lib);
4906
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
4907
                    WHERE iid = $lp";
4908
            $res = Database::query($sql);
4909
4910
            return $res;
4911
        }
4912
4913
        return false;
4914
    }
4915
4916
    /**
4917
     * Sets the name of the LP maker (publisher) (and save).
4918
     *
4919
     * @param string $name Optional string giving the new content_maker of this learnpath
4920
     *
4921
     * @return bool True
4922
     */
4923
    public function set_maker($name = '')
4924
    {
4925
        if (empty($name)) {
4926
            return false;
4927
        }
4928
        $this->maker = $name;
4929
        $table = Database::get_course_table(TABLE_LP_MAIN);
4930
        $lp_id = $this->get_id();
4931
        $sql = "UPDATE $table SET
4932
                content_maker = '".Database::escape_string($this->maker)."'
4933
                WHERE iid = $lp_id";
4934
        Database::query($sql);
4935
4936
        return true;
4937
    }
4938
4939
    /**
4940
     * Sets the name of the current learnpath (and save).
4941
     *
4942
     * @param string $name Optional string giving the new name of this learnpath
4943
     *
4944
     * @return bool True/False
4945
     */
4946
    public function set_name($name = null)
4947
    {
4948
        if (empty($name)) {
4949
            return false;
4950
        }
4951
        $this->name = $name;
4952
4953
        $lp_id = $this->get_id();
4954
4955
        $repo = Container::getLpRepository();
4956
        /** @var CLp $lp */
4957
        $lp = $repo->find($lp_id);
4958
        $lp->setName($name);
4959
        $repo->updateNodeForResource($lp);
4960
4961
        /*
4962
        $course_id = $this->course_info['real_id'];
4963
        $sql = "UPDATE $lp_table SET
4964
            name = '$name'
4965
            WHERE iid = $lp_id";
4966
        $result = Database::query($sql);
4967
        // If the lp is visible on the homepage, change his name there.
4968
        if (Database::affected_rows($result)) {
4969
        $session_id = api_get_session_id();
4970
        $session_condition = api_get_session_condition($session_id);
4971
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4972
        $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4973
        $sql = "UPDATE $tbl_tool SET name = '$name'
4974
        	    WHERE
4975
        	        c_id = $course_id AND
4976
        	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
4977
        Database::query($sql);*/
4978
4979
        //return true;
4980
        //}
4981
4982
        return false;
4983
    }
4984
4985
    /**
4986
     * Set index specified prefix terms for all items in this path.
4987
     *
4988
     * @param string $terms_string Comma-separated list of terms
4989
     * @param string $prefix       Xapian term prefix
4990
     *
4991
     * @return bool False on error, true otherwise
4992
     */
4993
    public function set_terms_by_prefix($terms_string, $prefix)
4994
    {
4995
        $course_id = api_get_course_int_id();
4996
        if (api_get_setting('search_enabled') !== 'true') {
4997
            return false;
4998
        }
4999
5000
        if (!extension_loaded('xapian')) {
5001
            return false;
5002
        }
5003
5004
        $terms_string = trim($terms_string);
5005
        $terms = explode(',', $terms_string);
5006
        array_walk($terms, 'trim_value');
5007
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5008
5009
        // Don't do anything if no change, verify only at DB, not the search engine.
5010
        if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
5011
            return false;
5012
        }
5013
5014
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5015
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5016
5017
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5018
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5019
        $lp_id = (int) $_POST['lp_id'];
5020
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5021
        $result = Database::query($sql);
5022
        $di = new ChamiloIndexer();
5023
5024
        while ($lp_item = Database::fetch_array($result)) {
5025
            // Get search_did.
5026
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5027
            $sql = 'SELECT * FROM %s
5028
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5029
                    LIMIT 1';
5030
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5031
5032
            //echo $sql; echo '<br>';
5033
            $res = Database::query($sql);
5034
            if (Database::num_rows($res) > 0) {
5035
                $se_ref = Database::fetch_array($res);
5036
                // Compare terms.
5037
                $doc = $di->get_document($se_ref['search_did']);
5038
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5039
                $xterms = [];
5040
                foreach ($xapian_terms as $xapian_term) {
5041
                    $xterms[] = substr($xapian_term['name'], 1);
5042
                }
5043
5044
                $dterms = $terms;
5045
                $missing_terms = array_diff($dterms, $xterms);
5046
                $deprecated_terms = array_diff($xterms, $dterms);
5047
5048
                // Save it to search engine.
5049
                foreach ($missing_terms as $term) {
5050
                    $doc->add_term($prefix.$term, 1);
5051
                }
5052
                foreach ($deprecated_terms as $term) {
5053
                    $doc->remove_term($prefix.$term);
5054
                }
5055
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5056
                $di->getDb()->flush();
5057
            }
5058
        }
5059
5060
        return true;
5061
    }
5062
5063
    /**
5064
     * Sets the theme of the LP (local/remote) (and save).
5065
     *
5066
     * @param string $name Optional string giving the new theme of this learnpath
5067
     *
5068
     * @return bool Returns true if theme name is not empty
5069
     */
5070
    public function set_theme($name = '')
5071
    {
5072
        $this->theme = $name;
5073
        $table = Database::get_course_table(TABLE_LP_MAIN);
5074
        $lp_id = $this->get_id();
5075
        $sql = "UPDATE $table
5076
                SET theme = '".Database::escape_string($this->theme)."'
5077
                WHERE iid = $lp_id";
5078
        Database::query($sql);
5079
5080
        return true;
5081
    }
5082
5083
    /**
5084
     * Sets the image of an LP (and save).
5085
     *
5086
     * @param string $name Optional string giving the new image of this learnpath
5087
     *
5088
     * @return bool Returns true if theme name is not empty
5089
     */
5090
    public function set_preview_image($name = '')
5091
    {
5092
        $this->preview_image = $name;
5093
        $table = Database::get_course_table(TABLE_LP_MAIN);
5094
        $lp_id = $this->get_id();
5095
        $sql = "UPDATE $table SET
5096
                preview_image = '".Database::escape_string($this->preview_image)."'
5097
                WHERE iid = $lp_id";
5098
        Database::query($sql);
5099
5100
        return true;
5101
    }
5102
5103
    /**
5104
     * Sets the author of a LP (and save).
5105
     *
5106
     * @param string $name Optional string giving the new author of this learnpath
5107
     *
5108
     * @return bool Returns true if author's name is not empty
5109
     */
5110
    public function set_author($name = '')
5111
    {
5112
        $this->author = $name;
5113
        $table = Database::get_course_table(TABLE_LP_MAIN);
5114
        $lp_id = $this->get_id();
5115
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5116
                WHERE iid = $lp_id";
5117
        Database::query($sql);
5118
5119
        return true;
5120
    }
5121
5122
    /**
5123
     * Sets the hide_toc_frame parameter of a LP (and save).
5124
     *
5125
     * @param int $hide 1 if frame is hidden 0 then else
5126
     *
5127
     * @return bool Returns true if author's name is not empty
5128
     */
5129
    public function set_hide_toc_frame($hide)
5130
    {
5131
        if (intval($hide) == $hide) {
5132
            $this->hide_toc_frame = $hide;
5133
            $table = Database::get_course_table(TABLE_LP_MAIN);
5134
            $lp_id = $this->get_id();
5135
            $sql = "UPDATE $table SET
5136
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5137
                    WHERE iid = $lp_id";
5138
            Database::query($sql);
5139
5140
            return true;
5141
        }
5142
5143
        return false;
5144
    }
5145
5146
    /**
5147
     * Sets the prerequisite of a LP (and save).
5148
     *
5149
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5150
     *
5151
     * @return bool returns true if prerequisite is not empty
5152
     */
5153
    public function set_prerequisite($prerequisite)
5154
    {
5155
        $this->prerequisite = (int) $prerequisite;
5156
        $table = Database::get_course_table(TABLE_LP_MAIN);
5157
        $lp_id = $this->get_id();
5158
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5159
                WHERE iid = $lp_id";
5160
        Database::query($sql);
5161
5162
        return true;
5163
    }
5164
5165
    /**
5166
     * Sets the location/proximity of the LP (local/remote) (and save).
5167
     *
5168
     * @param string $name Optional string giving the new location of this learnpath
5169
     *
5170
     * @return bool True on success / False on error
5171
     */
5172
    public function set_proximity($name = '')
5173
    {
5174
        if (empty($name)) {
5175
            return false;
5176
        }
5177
5178
        $this->proximity = $name;
5179
        $table = Database::get_course_table(TABLE_LP_MAIN);
5180
        $lp_id = $this->get_id();
5181
        $sql = "UPDATE $table SET
5182
                    content_local = '".Database::escape_string($name)."'
5183
                WHERE iid = $lp_id";
5184
        Database::query($sql);
5185
5186
        return true;
5187
    }
5188
5189
    /**
5190
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5191
     *
5192
     * @param int $id DB ID of the item
5193
     */
5194
    public function set_previous_item($id)
5195
    {
5196
        if ($this->debug > 0) {
5197
            error_log('In learnpath::set_previous_item()', 0);
5198
        }
5199
        $this->last = $id;
5200
    }
5201
5202
    /**
5203
     * Sets use_max_score.
5204
     *
5205
     * @param int $use_max_score Optional string giving the new location of this learnpath
5206
     *
5207
     * @return bool True on success / False on error
5208
     */
5209
    public function set_use_max_score($use_max_score = 1)
5210
    {
5211
        $use_max_score = (int) $use_max_score;
5212
        $this->use_max_score = $use_max_score;
5213
        $table = Database::get_course_table(TABLE_LP_MAIN);
5214
        $lp_id = $this->get_id();
5215
        $sql = "UPDATE $table SET
5216
                    use_max_score = '".$this->use_max_score."'
5217
                WHERE iid = $lp_id";
5218
        Database::query($sql);
5219
5220
        return true;
5221
    }
5222
5223
    /**
5224
     * Sets and saves the expired_on date.
5225
     *
5226
     * @param string $expired_on Optional string giving the new author of this learnpath
5227
     *
5228
     * @throws \Doctrine\ORM\OptimisticLockException
5229
     *
5230
     * @return bool Returns true if author's name is not empty
5231
     */
5232
    public function set_expired_on($expired_on)
5233
    {
5234
        $em = Database::getManager();
5235
        /** @var CLp $lp */
5236
        $lp = $em
5237
            ->getRepository('ChamiloCourseBundle:CLp')
5238
            ->findOneBy(
5239
                [
5240
                    'iid' => $this->get_id(),
5241
                ]
5242
            );
5243
5244
        if (!$lp) {
5245
            return false;
5246
        }
5247
5248
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5249
5250
        $lp->setExpiredOn($this->expired_on);
5251
        $em->persist($lp);
5252
        $em->flush();
5253
5254
        return true;
5255
    }
5256
5257
    /**
5258
     * Sets and saves the publicated_on date.
5259
     *
5260
     * @param string $publicated_on Optional string giving the new author of this learnpath
5261
     *
5262
     * @throws \Doctrine\ORM\OptimisticLockException
5263
     *
5264
     * @return bool Returns true if author's name is not empty
5265
     */
5266
    public function set_publicated_on($publicated_on)
5267
    {
5268
        $em = Database::getManager();
5269
        /** @var CLp $lp */
5270
        $lp = $em
5271
            ->getRepository('ChamiloCourseBundle:CLp')
5272
            ->findOneBy(
5273
                [
5274
                    'iid' => $this->get_id(),
5275
                ]
5276
            );
5277
5278
        if (!$lp) {
5279
            return false;
5280
        }
5281
5282
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5283
        $lp->setPublicatedOn($this->publicated_on);
5284
        $em->persist($lp);
5285
        $em->flush();
5286
5287
        return true;
5288
    }
5289
5290
    /**
5291
     * Sets and saves the expired_on date.
5292
     *
5293
     * @return bool Returns true if author's name is not empty
5294
     */
5295
    public function set_modified_on()
5296
    {
5297
        $this->modified_on = api_get_utc_datetime();
5298
        $table = Database::get_course_table(TABLE_LP_MAIN);
5299
        $lp_id = $this->get_id();
5300
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5301
                WHERE iid = $lp_id";
5302
        Database::query($sql);
5303
5304
        return true;
5305
    }
5306
5307
    /**
5308
     * Sets the object's error message.
5309
     *
5310
     * @param string $error Error message. If empty, reinits the error string
5311
     */
5312
    public function set_error_msg($error = '')
5313
    {
5314
        if ($this->debug > 0) {
5315
            error_log('In learnpath::set_error_msg()', 0);
5316
        }
5317
        if (empty($error)) {
5318
            $this->error = '';
5319
        } else {
5320
            $this->error .= $error;
5321
        }
5322
    }
5323
5324
    /**
5325
     * Launches the current item if not 'sco'
5326
     * (starts timer and make sure there is a record ready in the DB).
5327
     *
5328
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5329
     *
5330
     * @return bool
5331
     */
5332
    public function start_current_item($allow_new_attempt = false)
5333
    {
5334
        $debug = $this->debug;
5335
        if ($debug) {
5336
            error_log('In learnpath::start_current_item()');
5337
            error_log('current: '.$this->current);
5338
        }
5339
        if ($this->current != 0 && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5340
            $type = $this->get_type();
5341
            $item_type = $this->items[$this->current]->get_type();
5342
            if (($type == 2 && $item_type != 'sco') ||
5343
                ($type == 3 && $item_type != 'au') ||
5344
                ($type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES)
5345
            ) {
5346
                if ($debug) {
5347
                    error_log('item type: '.$item_type);
5348
                    error_log('lp type: '.$type);
5349
                }
5350
                $this->items[$this->current]->open($allow_new_attempt);
5351
                $this->autocomplete_parents($this->current);
5352
                $prereq_check = $this->prerequisites_match($this->current);
5353
                if ($debug) {
5354
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5355
                }
5356
                $this->items[$this->current]->save(false, $prereq_check);
5357
            }
5358
            // If sco, then it is supposed to have been updated by some other call.
5359
            if ($item_type == 'sco') {
5360
                $this->items[$this->current]->restart();
5361
            }
5362
        }
5363
        if ($debug) {
5364
            error_log('lp_view_session_id');
5365
            error_log($this->lp_view_session_id);
5366
            error_log('api session id');
5367
            error_log(api_get_session_id());
5368
            error_log('End of learnpath::start_current_item()');
5369
        }
5370
5371
        return true;
5372
    }
5373
5374
    /**
5375
     * Stops the processing and counters for the old item (as held in $this->last).
5376
     *
5377
     * @return bool True/False
5378
     */
5379
    public function stop_previous_item()
5380
    {
5381
        $debug = $this->debug;
5382
        if ($debug) {
5383
            error_log('In learnpath::stop_previous_item()', 0);
5384
        }
5385
5386
        if ($this->last != 0 && $this->last != $this->current &&
5387
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5388
        ) {
5389
            if ($debug) {
5390
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5391
            }
5392
            switch ($this->get_type()) {
5393
                case '3':
5394
                    if ($this->items[$this->last]->get_type() != 'au') {
5395
                        if ($debug) {
5396
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5397
                        }
5398
                        $this->items[$this->last]->close();
5399
                    } else {
5400
                        if ($debug) {
5401
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5402
                        }
5403
                    }
5404
                    break;
5405
                case '2':
5406
                    if ($this->items[$this->last]->get_type() != 'sco') {
5407
                        if ($debug) {
5408
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5409
                        }
5410
                        $this->items[$this->last]->close();
5411
                    } else {
5412
                        if ($debug) {
5413
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5414
                        }
5415
                    }
5416
                    break;
5417
                case '1':
5418
                default:
5419
                    if ($debug) {
5420
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5421
                    }
5422
                    $this->items[$this->last]->close();
5423
                    break;
5424
            }
5425
        } else {
5426
            if ($debug) {
5427
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5428
            }
5429
5430
            return false;
5431
        }
5432
5433
        return true;
5434
    }
5435
5436
    /**
5437
     * Updates the default view mode from fullscreen to embedded and inversely.
5438
     *
5439
     * @return string The current default view mode ('fullscreen' or 'embedded')
5440
     */
5441
    public function update_default_view_mode()
5442
    {
5443
        $table = Database::get_course_table(TABLE_LP_MAIN);
5444
        $sql = "SELECT * FROM $table
5445
                WHERE iid = ".$this->get_id();
5446
        $res = Database::query($sql);
5447
        if (Database::num_rows($res) > 0) {
5448
            $row = Database::fetch_array($res);
5449
            $default_view_mode = $row['default_view_mod'];
5450
            $view_mode = $default_view_mode;
5451
            switch ($default_view_mode) {
5452
                case 'fullscreen': // default with popup
5453
                    $view_mode = 'embedded';
5454
                    break;
5455
                case 'embedded': // default view with left menu
5456
                    $view_mode = 'embedframe';
5457
                    break;
5458
                case 'embedframe': //folded menu
5459
                    $view_mode = 'impress';
5460
                    break;
5461
                case 'impress':
5462
                    $view_mode = 'fullscreen';
5463
                    break;
5464
            }
5465
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5466
                    WHERE iid = ".$this->get_id();
5467
            Database::query($sql);
5468
            $this->mode = $view_mode;
5469
5470
            return $view_mode;
5471
        }
5472
5473
        return -1;
5474
    }
5475
5476
    /**
5477
     * Updates the default behaviour about auto-commiting SCORM updates.
5478
     *
5479
     * @return bool True if auto-commit has been set to 'on', false otherwise
5480
     */
5481
    public function update_default_scorm_commit()
5482
    {
5483
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5484
        $sql = "SELECT * FROM $lp_table
5485
                WHERE iid = ".$this->get_id();
5486
        $res = Database::query($sql);
5487
        if (Database::num_rows($res) > 0) {
5488
            $row = Database::fetch_array($res);
5489
            $force = $row['force_commit'];
5490
            if ($force == 1) {
5491
                $force = 0;
5492
                $force_return = false;
5493
            } elseif ($force == 0) {
5494
                $force = 1;
5495
                $force_return = true;
5496
            }
5497
            $sql = "UPDATE $lp_table SET force_commit = $force
5498
                    WHERE iid = ".$this->get_id();
5499
            Database::query($sql);
5500
            $this->force_commit = $force_return;
5501
5502
            return $force_return;
5503
        }
5504
5505
        return -1;
5506
    }
5507
5508
    /**
5509
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5510
     *
5511
     * @return bool True on success, false on failure
5512
     */
5513
    public function update_display_order()
5514
    {
5515
        $course_id = api_get_course_int_id();
5516
        $table = Database::get_course_table(TABLE_LP_MAIN);
5517
        $sql = "SELECT * FROM $table
5518
                WHERE c_id = $course_id
5519
                ORDER BY display_order";
5520
        $res = Database::query($sql);
5521
        if ($res === false) {
5522
            return false;
5523
        }
5524
5525
        $num = Database::num_rows($res);
5526
        // First check the order is correct, globally (might be wrong because
5527
        // of versions < 1.8.4).
5528
        if ($num > 0) {
5529
            $i = 1;
5530
            while ($row = Database::fetch_array($res)) {
5531
                if ($row['display_order'] != $i) {
5532
                    // If we find a gap in the order, we need to fix it.
5533
                    $sql = "UPDATE $table SET display_order = $i
5534
                            WHERE iid = ".$row['iid'];
5535
                    Database::query($sql);
5536
                }
5537
                $i++;
5538
            }
5539
        }
5540
5541
        return true;
5542
    }
5543
5544
    /**
5545
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5546
     *
5547
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5548
     */
5549
    public function update_reinit()
5550
    {
5551
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5552
        $sql = "SELECT * FROM $lp_table
5553
                WHERE iid = ".$this->get_id();
5554
        $res = Database::query($sql);
5555
        if (Database::num_rows($res) > 0) {
5556
            $row = Database::fetch_array($res);
5557
            $force = $row['prevent_reinit'];
5558
            if ($force == 1) {
5559
                $force = 0;
5560
            } elseif ($force == 0) {
5561
                $force = 1;
5562
            }
5563
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5564
                    WHERE iid = ".$this->get_id();
5565
            Database::query($sql);
5566
            $this->prevent_reinit = $force;
5567
5568
            return $force;
5569
        }
5570
5571
        return -1;
5572
    }
5573
5574
    /**
5575
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5576
     *
5577
     * @return string 'single', 'multi' or 'seriousgame'
5578
     *
5579
     * @author ndiechburg <[email protected]>
5580
     */
5581
    public function get_attempt_mode()
5582
    {
5583
        //Set default value for seriousgame_mode
5584
        if (!isset($this->seriousgame_mode)) {
5585
            $this->seriousgame_mode = 0;
5586
        }
5587
        // Set default value for prevent_reinit
5588
        if (!isset($this->prevent_reinit)) {
5589
            $this->prevent_reinit = 1;
5590
        }
5591
        if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
5592
            return 'seriousgame';
5593
        }
5594
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
5595
            return 'single';
5596
        }
5597
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
5598
            return 'multiple';
5599
        }
5600
5601
        return 'single';
5602
    }
5603
5604
    /**
5605
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5606
     *
5607
     * @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...
5608
     *
5609
     * @return bool
5610
     *
5611
     * @author ndiechburg <[email protected]>
5612
     */
5613
    public function set_attempt_mode($mode)
5614
    {
5615
        switch ($mode) {
5616
            case 'seriousgame':
5617
                $sg_mode = 1;
5618
                $prevent_reinit = 1;
5619
                break;
5620
            case 'single':
5621
                $sg_mode = 0;
5622
                $prevent_reinit = 1;
5623
                break;
5624
            case 'multiple':
5625
                $sg_mode = 0;
5626
                $prevent_reinit = 0;
5627
                break;
5628
            default:
5629
                $sg_mode = 0;
5630
                $prevent_reinit = 0;
5631
                break;
5632
        }
5633
        $this->prevent_reinit = $prevent_reinit;
5634
        $this->seriousgame_mode = $sg_mode;
5635
        $table = Database::get_course_table(TABLE_LP_MAIN);
5636
        $sql = "UPDATE $table SET
5637
                prevent_reinit = $prevent_reinit ,
5638
                seriousgame_mode = $sg_mode
5639
                WHERE iid = ".$this->get_id();
5640
        $res = Database::query($sql);
5641
        if ($res) {
5642
            return true;
5643
        } else {
5644
            return false;
5645
        }
5646
    }
5647
5648
    /**
5649
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5650
     *
5651
     * @author ndiechburg <[email protected]>
5652
     */
5653
    public function switch_attempt_mode()
5654
    {
5655
        $mode = $this->get_attempt_mode();
5656
        switch ($mode) {
5657
            case 'single':
5658
                $next_mode = 'multiple';
5659
                break;
5660
            case 'multiple':
5661
                $next_mode = 'seriousgame';
5662
                break;
5663
            case 'seriousgame':
5664
            default:
5665
                $next_mode = 'single';
5666
                break;
5667
        }
5668
        $this->set_attempt_mode($next_mode);
5669
    }
5670
5671
    /**
5672
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5673
     * but possibility to do again a completed item.
5674
     *
5675
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5676
     *
5677
     * @author ndiechburg <[email protected]>
5678
     */
5679
    public function set_seriousgame_mode()
5680
    {
5681
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5682
        $sql = "SELECT * FROM $lp_table
5683
                WHERE iid = ".$this->get_id();
5684
        $res = Database::query($sql);
5685
        if (Database::num_rows($res) > 0) {
5686
            $row = Database::fetch_array($res);
5687
            $force = $row['seriousgame_mode'];
5688
            if ($force == 1) {
5689
                $force = 0;
5690
            } elseif ($force == 0) {
5691
                $force = 1;
5692
            }
5693
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5694
			        WHERE iid = ".$this->get_id();
5695
            Database::query($sql);
5696
            $this->seriousgame_mode = $force;
5697
5698
            return $force;
5699
        }
5700
5701
        return -1;
5702
    }
5703
5704
    /**
5705
     * Updates the "scorm_debug" value that shows or hide the debug window.
5706
     *
5707
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5708
     */
5709
    public function update_scorm_debug()
5710
    {
5711
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5712
        $sql = "SELECT * FROM $lp_table
5713
                WHERE iid = ".$this->get_id();
5714
        $res = Database::query($sql);
5715
        if (Database::num_rows($res) > 0) {
5716
            $row = Database::fetch_array($res);
5717
            $force = $row['debug'];
5718
            if ($force == 1) {
5719
                $force = 0;
5720
            } elseif ($force == 0) {
5721
                $force = 1;
5722
            }
5723
            $sql = "UPDATE $lp_table SET debug = $force
5724
                    WHERE iid = ".$this->get_id();
5725
            Database::query($sql);
5726
            $this->scorm_debug = $force;
5727
5728
            return $force;
5729
        }
5730
5731
        return -1;
5732
    }
5733
5734
    /**
5735
     * Function that makes a call to the function sort_tree_array and create_tree_array.
5736
     *
5737
     * @author Kevin Van Den Haute
5738
     *
5739
     * @param  array
5740
     */
5741
    public function tree_array($array)
5742
    {
5743
        $array = $this->sort_tree_array($array);
5744
        $this->create_tree_array($array);
5745
    }
5746
5747
    /**
5748
     * Creates an array with the elements of the learning path tree in it.
5749
     *
5750
     * @author Kevin Van Den Haute
5751
     *
5752
     * @param array $array
5753
     * @param int   $parent
5754
     * @param int   $depth
5755
     * @param array $tmp
5756
     */
5757
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
5758
    {
5759
        if (is_array($array)) {
5760
            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...
5761
                if ($array[$i]['parent_item_id'] == $parent) {
5762
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
5763
                        $tmp[] = $array[$i]['parent_item_id'];
5764
                        $depth++;
5765
                    }
5766
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
5767
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
5768
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
5769
5770
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
5771
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
5772
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
5773
                    $this->arrMenu[] = [
5774
                        'id' => $array[$i]['id'],
5775
                        'ref' => $ref,
5776
                        'item_type' => $array[$i]['item_type'],
5777
                        'title' => $array[$i]['title'],
5778
                        'title_raw' => $array[$i]['title_raw'],
5779
                        'path' => $path,
5780
                        'description' => $array[$i]['description'],
5781
                        'parent_item_id' => $array[$i]['parent_item_id'],
5782
                        'previous_item_id' => $array[$i]['previous_item_id'],
5783
                        'next_item_id' => $array[$i]['next_item_id'],
5784
                        'min_score' => $array[$i]['min_score'],
5785
                        'max_score' => $array[$i]['max_score'],
5786
                        'mastery_score' => $array[$i]['mastery_score'],
5787
                        'display_order' => $array[$i]['display_order'],
5788
                        'prerequisite' => $preq,
5789
                        'depth' => $depth,
5790
                        'audio' => $audio,
5791
                        'prerequisite_min_score' => $prerequisiteMinScore,
5792
                        'prerequisite_max_score' => $prerequisiteMaxScore,
5793
                    ];
5794
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
5795
                }
5796
            }
5797
        }
5798
    }
5799
5800
    /**
5801
     * Sorts a multi dimensional array by parent id and display order.
5802
     *
5803
     * @author Kevin Van Den Haute
5804
     *
5805
     * @param array $array (array with al the learning path items in it)
5806
     *
5807
     * @return array
5808
     */
5809
    public function sort_tree_array($array)
5810
    {
5811
        foreach ($array as $key => $row) {
5812
            $parent[$key] = $row['parent_item_id'];
5813
            $position[$key] = $row['display_order'];
5814
        }
5815
5816
        if (count($array) > 0) {
5817
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
5818
        }
5819
5820
        return $array;
5821
    }
5822
5823
    /**
5824
     * Function that creates a html list of learning path items so that we can add audio files to them.
5825
     *
5826
     * @author Kevin Van Den Haute
5827
     *
5828
     * @return string
5829
     */
5830
    public function overview()
5831
    {
5832
        $return = '';
5833
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
5834
5835
        // we need to start a form when we want to update all the mp3 files
5836
        if ($update_audio == 'true') {
5837
            $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">';
5838
        }
5839
        $return .= '<div id="message"></div>';
5840
        if (count($this->items) == 0) {
5841
            $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');
5842
        } else {
5843
            $return_audio = '<table class="data_table">';
5844
            $return_audio .= '<tr>';
5845
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
5846
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
5847
            $return_audio .= '</tr>';
5848
5849
            if ($update_audio != 'true') {
5850
                $return .= '<div class="col-md-12">';
5851
                $return .= self::return_new_tree($update_audio);
5852
                $return .= '</div>';
5853
                $return .= Display::div(
5854
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
5855
                    ['style' => 'float:left; margin-top:15px;width:100%']
5856
                );
5857
            } else {
5858
                $return_audio .= self::return_new_tree($update_audio);
5859
                $return .= $return_audio.'</table>';
5860
            }
5861
5862
            // We need to close the form when we are updating the mp3 files.
5863
            if ($update_audio == 'true') {
5864
                $return .= '<div class="footer-audio">';
5865
                $return .= Display::button(
5866
                    'save_audio',
5867
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('Save audio and organization'),
5868
                    ['class' => 'btn btn-primary', 'type' => 'submit']
5869
                );
5870
                $return .= '</div>';
5871
            }
5872
        }
5873
5874
        // We need to close the form when we are updating the mp3 files.
5875
        if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
5876
            $return .= '</form>';
5877
        }
5878
5879
        return $return;
5880
    }
5881
5882
    /**
5883
     * @param string $update_audio
5884
     *
5885
     * @return array
5886
     */
5887
    public function processBuildMenuElements($update_audio = 'false')
5888
    {
5889
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
5890
        $arrLP = $this->getItemsForForm();
5891
5892
        $this->tree_array($arrLP);
5893
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
5894
        unset($this->arrMenu);
5895
        $default_data = null;
5896
        $default_content = null;
5897
        $elements = [];
5898
        $return_audio = null;
5899
        $iconPath = api_get_path(SYS_CODE_PATH).'img/';
5900
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
5901
        $countItems = count($arrLP);
5902
5903
        $upIcon = Display::return_icon(
5904
            'up.png',
5905
            get_lang('Up'),
5906
            [],
5907
            ICON_SIZE_TINY
5908
        );
5909
5910
        $disableUpIcon = Display::return_icon(
5911
            'up_na.png',
5912
            get_lang('Up'),
5913
            [],
5914
            ICON_SIZE_TINY
5915
        );
5916
5917
        $downIcon = Display::return_icon(
5918
            'down.png',
5919
            get_lang('Down'),
5920
            [],
5921
            ICON_SIZE_TINY
5922
        );
5923
5924
        $disableDownIcon = Display::return_icon(
5925
            'down_na.png',
5926
            get_lang('Down'),
5927
            [],
5928
            ICON_SIZE_TINY
5929
        );
5930
5931
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
5932
5933
        $pluginCalendar = api_get_plugin_setting('learning_calendar', 'enabled') === 'true';
5934
        $plugin = null;
5935
        if ($pluginCalendar) {
5936
            $plugin = LearningCalendarPlugin::create();
5937
        }
5938
5939
        for ($i = 0; $i < $countItems; $i++) {
5940
            $parent_id = $arrLP[$i]['parent_item_id'];
5941
            $title = $arrLP[$i]['title'];
5942
            $title_cut = $arrLP[$i]['title_raw'];
5943
            if ($show === false) {
5944
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
5945
            }
5946
            // Link for the documents
5947
            if ($arrLP[$i]['item_type'] === 'document' || $arrLP[$i]['item_type'] == TOOL_READOUT_TEXT) {
5948
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5949
                $title_cut = Display::url(
5950
                    $title_cut,
5951
                    $url,
5952
                    [
5953
                        'class' => 'ajax moved',
5954
                        'data-title' => $title,
5955
                        'title' => $title,
5956
                    ]
5957
                );
5958
            }
5959
5960
            // Detect if type is FINAL_ITEM to set path_id to SESSION
5961
            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
5962
                Session::write('pathItem', $arrLP[$i]['path']);
5963
            }
5964
5965
            $oddClass = 'row_even';
5966
            if (($i % 2) == 0) {
5967
                $oddClass = 'row_odd';
5968
            }
5969
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
5970
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
5971
5972
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
5973
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
5974
            } else {
5975
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
5976
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
5977
                } else {
5978
                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
5979
                        $icon = Display::return_icon('certificate.png');
5980
                    } else {
5981
                        $icon = Display::return_icon('folder_document.png');
5982
                    }
5983
                }
5984
            }
5985
5986
            // The audio column.
5987
            $return_audio .= '<td align="left" style="padding-left:10px;">';
5988
            $audio = '';
5989
            if (!$update_audio || $update_audio != 'true') {
5990
                if (empty($arrLP[$i]['audio'])) {
5991
                    $audio .= '';
5992
                }
5993
            } else {
5994
                $types = self::getChapterTypes();
5995
                if (!in_array($arrLP[$i]['item_type'], $types)) {
5996
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
5997
                    if (!empty($arrLP[$i]['audio'])) {
5998
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
5999
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('Remove audio');
6000
                    }
6001
                }
6002
            }
6003
6004
            $return_audio .= Display::span($icon.' '.$title).
6005
                Display::tag(
6006
                    'td',
6007
                    $audio,
6008
                    ['style' => '']
6009
                );
6010
            $return_audio .= '</td>';
6011
            $move_icon = '';
6012
            $move_item_icon = '';
6013
            $edit_icon = '';
6014
            $delete_icon = '';
6015
            $audio_icon = '';
6016
            $prerequisities_icon = '';
6017
            $forumIcon = '';
6018
            $previewIcon = '';
6019
            $pluginCalendarIcon = '';
6020
            $orderIcons = '';
6021
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
6022
6023
            if ($is_allowed_to_edit) {
6024
                if (!$update_audio || $update_audio != 'true') {
6025
                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
6026
                        $move_icon .= '<a class="moved" href="#">';
6027
                        $move_icon .= Display::return_icon(
6028
                            'move_everywhere.png',
6029
                            get_lang('Move'),
6030
                            [],
6031
                            ICON_SIZE_TINY
6032
                        );
6033
                        $move_icon .= '</a>';
6034
                    }
6035
                }
6036
6037
                // No edit for this item types
6038
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6039
                    if ($arrLP[$i]['item_type'] != 'dir') {
6040
                        $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">';
6041
                        $edit_icon .= Display::return_icon(
6042
                            'edit.png',
6043
                            get_lang('Edit section description/name'),
6044
                            [],
6045
                            ICON_SIZE_TINY
6046
                        );
6047
                        $edit_icon .= '</a>';
6048
6049
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6050
                            $forumThread = null;
6051
                            if (isset($this->items[$arrLP[$i]['id']])) {
6052
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6053
                                    $this->course_int_id,
6054
                                    $this->lp_session_id
6055
                                );
6056
                            }
6057
                            if ($forumThread) {
6058
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6059
                                        'action' => 'dissociate_forum',
6060
                                        'id' => $arrLP[$i]['id'],
6061
                                        'lp_id' => $this->lp_id,
6062
                                    ]);
6063
                                $forumIcon = Display::url(
6064
                                    Display::return_icon(
6065
                                        'forum.png',
6066
                                        get_lang('Dissociate the forum of this learning path item'),
6067
                                        [],
6068
                                        ICON_SIZE_TINY
6069
                                    ),
6070
                                    $forumIconUrl,
6071
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6072
                                );
6073
                            } else {
6074
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6075
                                        'action' => 'create_forum',
6076
                                        'id' => $arrLP[$i]['id'],
6077
                                        'lp_id' => $this->lp_id,
6078
                                    ]);
6079
                                $forumIcon = Display::url(
6080
                                    Display::return_icon(
6081
                                        'forum.png',
6082
                                        get_lang('Associate a forum to this learning path item'),
6083
                                        [],
6084
                                        ICON_SIZE_TINY
6085
                                    ),
6086
                                    $forumIconUrl,
6087
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6088
                                );
6089
                            }
6090
                        }
6091
                    } else {
6092
                        $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">';
6093
                        $edit_icon .= Display::return_icon(
6094
                            'edit.png',
6095
                            get_lang('Edit section description/name'),
6096
                            [],
6097
                            ICON_SIZE_TINY
6098
                        );
6099
                        $edit_icon .= '</a>';
6100
                    }
6101
                } else {
6102
                    if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6103
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6104
                        $edit_icon .= Display::return_icon(
6105
                            'edit.png',
6106
                            get_lang('Edit'),
6107
                            [],
6108
                            ICON_SIZE_TINY
6109
                        );
6110
                        $edit_icon .= '</a>';
6111
                    }
6112
                }
6113
6114
                if ($pluginCalendar) {
6115
                    $pluginLink = $pluginUrl.
6116
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6117
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('1 day'), [], ICON_SIZE_TINY);
6118
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6119
                    if ($itemInfo && $itemInfo['value'] == 1) {
6120
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('1 day'), [], ICON_SIZE_TINY);
6121
                    }
6122
                    $pluginCalendarIcon = Display::url(
6123
                        $iconCalendar,
6124
                        $pluginLink,
6125
                        ['class' => 'btn btn-default']
6126
                    );
6127
                }
6128
6129
                if ($arrLP[$i]['item_type'] != 'final_item') {
6130
                    $orderIcons = Display::url(
6131
                        $upIcon,
6132
                        'javascript:void(0)',
6133
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
6134
                    );
6135
                    $orderIcons .= Display::url(
6136
                        $downIcon,
6137
                        'javascript:void(0)',
6138
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
6139
                    );
6140
                }
6141
6142
                $delete_icon .= ' <a
6143
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
6144
                    onclick="return confirmation(\''.addslashes($title).'\');"
6145
                    class="btn btn-default">';
6146
                $delete_icon .= Display::return_icon(
6147
                    'delete.png',
6148
                    get_lang('Delete section'),
6149
                    [],
6150
                    ICON_SIZE_TINY
6151
                );
6152
                $delete_icon .= '</a>';
6153
6154
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6155
                $previewImage = Display::return_icon(
6156
                    'preview_view.png',
6157
                    get_lang('Preview'),
6158
                    [],
6159
                    ICON_SIZE_TINY
6160
                );
6161
6162
                switch ($arrLP[$i]['item_type']) {
6163
                    case TOOL_DOCUMENT:
6164
                    case TOOL_LP_FINAL_ITEM:
6165
                    case TOOL_READOUT_TEXT:
6166
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6167
                        $previewIcon = Display::url(
6168
                            $previewImage,
6169
                            $urlPreviewLink,
6170
                            [
6171
                                'target' => '_blank',
6172
                                'class' => 'btn btn-default',
6173
                                'data-title' => $arrLP[$i]['title'],
6174
                                'title' => $arrLP[$i]['title'],
6175
                            ]
6176
                        );
6177
                        break;
6178
                    case TOOL_THREAD:
6179
                    case TOOL_FORUM:
6180
                    case TOOL_QUIZ:
6181
                    case TOOL_STUDENTPUBLICATION:
6182
                    case TOOL_LP_FINAL_ITEM:
6183
                    case TOOL_LINK:
6184
                        $class = 'btn btn-default';
6185
                        $target = '_blank';
6186
                        $link = self::rl_get_resource_link_for_learnpath(
6187
                            $this->course_int_id,
6188
                            $this->lp_id,
6189
                            $arrLP[$i]['id'],
6190
                            0
6191
                        );
6192
                        $previewIcon = Display::url(
6193
                            $previewImage,
6194
                            $link,
6195
                            [
6196
                                'class' => $class,
6197
                                'data-title' => $arrLP[$i]['title'],
6198
                                'title' => $arrLP[$i]['title'],
6199
                                'target' => $target,
6200
                            ]
6201
                        );
6202
                        break;
6203
                    default:
6204
                        $previewIcon = Display::url(
6205
                            $previewImage,
6206
                            $url.'&action=view_item',
6207
                            ['class' => 'btn btn-default', 'target' => '_blank']
6208
                        );
6209
                        break;
6210
                }
6211
6212
                if ($arrLP[$i]['item_type'] != 'dir') {
6213
                    $prerequisities_icon = Display::url(
6214
                        Display::return_icon(
6215
                            'accept.png',
6216
                            get_lang('Prerequisites'),
6217
                            [],
6218
                            ICON_SIZE_TINY
6219
                        ),
6220
                        $url.'&action=edit_item_prereq',
6221
                        ['class' => 'btn btn-default']
6222
                    );
6223
                    if ($arrLP[$i]['item_type'] != 'final_item') {
6224
                        $move_item_icon = Display::url(
6225
                            Display::return_icon(
6226
                                'move.png',
6227
                                get_lang('Move'),
6228
                                [],
6229
                                ICON_SIZE_TINY
6230
                            ),
6231
                            $url.'&action=move_item',
6232
                            ['class' => 'btn btn-default']
6233
                        );
6234
                    }
6235
                    $audio_icon = Display::url(
6236
                        Display::return_icon(
6237
                            'audio.png',
6238
                            get_lang('Upload'),
6239
                            [],
6240
                            ICON_SIZE_TINY
6241
                        ),
6242
                        $url.'&action=add_audio',
6243
                        ['class' => 'btn btn-default']
6244
                    );
6245
                }
6246
            }
6247
            if ($update_audio != 'true') {
6248
                $row = $move_icon.' '.$icon.
6249
                    Display::span($title_cut).
6250
                    Display::tag(
6251
                        'div',
6252
                        "<div class=\"btn-group btn-group-xs\">
6253
                                    $previewIcon
6254
                                    $audio
6255
                                    $edit_icon
6256
                                    $pluginCalendarIcon
6257
                                    $forumIcon
6258
                                    $prerequisities_icon
6259
                                    $move_item_icon
6260
                                    $audio_icon
6261
                                    $orderIcons
6262
                                    $delete_icon
6263
                                </div>",
6264
                        ['class' => 'btn-toolbar button_actions']
6265
                    );
6266
            } else {
6267
                $row =
6268
                    Display::span($title.$icon).
6269
                    Display::span($audio, ['class' => 'button_actions']);
6270
            }
6271
6272
            $default_data[$arrLP[$i]['id']] = $row;
6273
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6274
6275
            if (empty($parent_id)) {
6276
                $elements[$arrLP[$i]['id']]['data'] = $row;
6277
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6278
            } else {
6279
                $parent_arrays = [];
6280
                if ($arrLP[$i]['depth'] > 1) {
6281
                    // Getting list of parents
6282
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6283
                        foreach ($arrLP as $item) {
6284
                            if ($item['id'] == $parent_id) {
6285
                                if ($item['parent_item_id'] == 0) {
6286
                                    $parent_id = $item['id'];
6287
                                    break;
6288
                                } else {
6289
                                    $parent_id = $item['parent_item_id'];
6290
                                    if (empty($parent_arrays)) {
6291
                                        $parent_arrays[] = intval($item['id']);
6292
                                    }
6293
                                    $parent_arrays[] = $parent_id;
6294
                                    break;
6295
                                }
6296
                            }
6297
                        }
6298
                    }
6299
                }
6300
6301
                if (!empty($parent_arrays)) {
6302
                    $parent_arrays = array_reverse($parent_arrays);
6303
                    $val = '$elements';
6304
                    $x = 0;
6305
                    foreach ($parent_arrays as $item) {
6306
                        if ($x != count($parent_arrays) - 1) {
6307
                            $val .= '["'.$item.'"]["children"]';
6308
                        } else {
6309
                            $val .= '["'.$item.'"]["children"]';
6310
                        }
6311
                        $x++;
6312
                    }
6313
                    $val .= "";
6314
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6315
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6316
                } else {
6317
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6318
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6319
                }
6320
            }
6321
        }
6322
6323
        return [
6324
            'elements' => $elements,
6325
            'default_data' => $default_data,
6326
            'default_content' => $default_content,
6327
            'return_audio' => $return_audio,
6328
        ];
6329
    }
6330
6331
    /**
6332
     * @param string $updateAudio true/false strings
6333
     *
6334
     * @return string
6335
     */
6336
    public function returnLpItemList($updateAudio)
6337
    {
6338
        $result = $this->processBuildMenuElements($updateAudio);
6339
6340
        $html = self::print_recursive(
6341
            $result['elements'],
6342
            $result['default_data'],
6343
            $result['default_content']
6344
        );
6345
6346
        if (!empty($html)) {
6347
            $html .= Display::return_message(get_lang('Drag and drop an element here'));
6348
        }
6349
6350
        return $html;
6351
    }
6352
6353
    /**
6354
     * @param string $update_audio
6355
     * @param bool   $drop_element_here
6356
     *
6357
     * @return string
6358
     */
6359
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6360
    {
6361
        $result = $this->processBuildMenuElements($update_audio);
6362
6363
        $list = '<ul id="lp_item_list">';
6364
        $tree = $this->print_recursive(
6365
            $result['elements'],
6366
            $result['default_data'],
6367
            $result['default_content']
6368
        );
6369
6370
        if (!empty($tree)) {
6371
            $list .= $tree;
6372
        } else {
6373
            if ($drop_element_here) {
6374
                $list .= Display::return_message(get_lang('Drag and drop an element here'));
6375
            }
6376
        }
6377
        $list .= '</ul>';
6378
6379
        $return = Display::panelCollapse(
6380
            $this->name,
6381
            $list,
6382
            'scorm-list',
6383
            null,
6384
            'scorm-list-accordion',
6385
            'scorm-list-collapse'
6386
        );
6387
6388
        if ($update_audio === 'true') {
6389
            $return = $result['return_audio'];
6390
        }
6391
6392
        return $return;
6393
    }
6394
6395
    /**
6396
     * @param array $elements
6397
     * @param array $default_data
6398
     * @param array $default_content
6399
     *
6400
     * @return string
6401
     */
6402
    public function print_recursive($elements, $default_data, $default_content)
6403
    {
6404
        $return = '';
6405
        foreach ($elements as $key => $item) {
6406
            if (isset($item['load_data']) || empty($item['data'])) {
6407
                $item['data'] = $default_data[$item['load_data']];
6408
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6409
            }
6410
            $sub_list = '';
6411
            if (isset($item['type']) && $item['type'] === 'dir') {
6412
                // empty value
6413
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6414
            }
6415
            if (empty($item['children'])) {
6416
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6417
                $active = null;
6418
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6419
                    $active = 'active';
6420
                }
6421
                $return .= Display::tag(
6422
                    'li',
6423
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6424
                    ['id' => $key, 'class' => 'record li_container']
6425
                );
6426
            } else {
6427
                // Sections
6428
                $data = '';
6429
                if (isset($item['children'])) {
6430
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6431
                }
6432
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6433
                $return .= Display::tag(
6434
                    'li',
6435
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6436
                    ['id' => $key, 'class' => 'record li_container']
6437
                );
6438
            }
6439
        }
6440
6441
        return $return;
6442
    }
6443
6444
    /**
6445
     * This function builds the action menu.
6446
     *
6447
     * @param bool $returnContent          Optional
6448
     * @param bool $showRequirementButtons Optional. Allow show the requirements button
6449
     * @param bool $isConfigPage           Optional. If is the config page, show the edit button
6450
     * @param bool $allowExpand            Optional. Allow show the expand/contract button
6451
     *
6452
     * @return string
6453
     */
6454
    public function build_action_menu(
6455
        $returnContent = false,
6456
        $showRequirementButtons = true,
6457
        $isConfigPage = false,
6458
        $allowExpand = true
6459
    ) {
6460
        $actionsRight = '';
6461
        $actionsLeft = Display::url(
6462
            Display::return_icon(
6463
                'back.png',
6464
                get_lang('Back to learning paths'),
6465
                '',
6466
                ICON_SIZE_MEDIUM
6467
            ),
6468
            'lp_controller.php?'.api_get_cidreq()
6469
        );
6470
        $actionsLeft .= Display::url(
6471
            Display::return_icon(
6472
                'preview_view.png',
6473
                get_lang('Preview'),
6474
                '',
6475
                ICON_SIZE_MEDIUM
6476
            ),
6477
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6478
                'action' => 'view',
6479
                'lp_id' => $this->lp_id,
6480
                'isStudentView' => 'true',
6481
            ])
6482
        );
6483
6484
        $actionsLeft .= Display::url(
6485
            Display::return_icon(
6486
                'upload_audio.png',
6487
                get_lang('Add audio'),
6488
                '',
6489
                ICON_SIZE_MEDIUM
6490
            ),
6491
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6492
                'action' => 'admin_view',
6493
                'lp_id' => $this->lp_id,
6494
                'updateaudio' => 'true',
6495
            ])
6496
        );
6497
6498
        $subscriptionSettings = self::getSubscriptionSettings();
6499
6500
        $request = api_request_uri();
6501
        if (strpos($request, 'edit') === false) {
6502
            $actionsLeft .= Display::url(
6503
                Display::return_icon(
6504
                    'settings.png',
6505
                    get_lang('Course settings'),
6506
                    '',
6507
                    ICON_SIZE_MEDIUM
6508
                ),
6509
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6510
                    'action' => 'edit',
6511
                    'lp_id' => $this->lp_id,
6512
                ])
6513
            );
6514
        }
6515
6516
        if (strpos($request, 'build') === false && strpos($request, 'add_item') === false) {
6517
            $actionsLeft .= Display::url(
6518
                Display::return_icon(
6519
                    'edit.png',
6520
                    get_lang('Edit'),
6521
                    '',
6522
                    ICON_SIZE_MEDIUM
6523
                ),
6524
                'lp_controller.php?'.http_build_query([
6525
                    'action' => 'build',
6526
                    'lp_id' => $this->lp_id,
6527
                ]).'&'.api_get_cidreq()
6528
            );
6529
        }
6530
6531
        if (strpos(api_get_self(), 'lp_subscribe_users.php') === false) {
6532
            if ($this->subscribeUsers == 1 &&
6533
                $subscriptionSettings['allow_add_users_to_lp']) {
6534
                $actionsLeft .= Display::url(
6535
                    Display::return_icon(
6536
                        'user.png',
6537
                        get_lang('Subscribe users to learning path'),
6538
                        '',
6539
                        ICON_SIZE_MEDIUM
6540
                    ),
6541
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=".$this->lp_id."&".api_get_cidreq()
6542
                );
6543
            }
6544
        }
6545
6546
        if ($allowExpand) {
6547
            $actionsLeft .= Display::url(
6548
                Display::return_icon(
6549
                    'expand.png',
6550
                    get_lang('Expand'),
6551
                    ['id' => 'expand'],
6552
                    ICON_SIZE_MEDIUM
6553
                ).
6554
                Display::return_icon(
6555
                    'contract.png',
6556
                    get_lang('Collapse'),
6557
                    ['id' => 'contract', 'class' => 'hide'],
6558
                    ICON_SIZE_MEDIUM
6559
                ),
6560
                '#',
6561
                ['role' => 'button', 'id' => 'hide_bar_template']
6562
            );
6563
        }
6564
6565
        if ($showRequirementButtons) {
6566
            $buttons = [
6567
                [
6568
                    'title' => get_lang('Set previous step as prerequisite for each step'),
6569
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6570
                        'action' => 'set_previous_step_as_prerequisite',
6571
                        'lp_id' => $this->lp_id,
6572
                    ]),
6573
                ],
6574
                [
6575
                    'title' => get_lang('Clear all prerequisites'),
6576
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6577
                        'action' => 'clear_prerequisites',
6578
                        'lp_id' => $this->lp_id,
6579
                    ]),
6580
                ],
6581
            ];
6582
            $actionsRight = Display::groupButtonWithDropDown(
6583
                get_lang('Prerequisites options'),
6584
                $buttons,
6585
                true
6586
            );
6587
        }
6588
6589
        $toolbar = Display::toolbarAction(
6590
            'actions-lp-controller',
6591
            [$actionsLeft, $actionsRight]
6592
        );
6593
6594
        if ($returnContent) {
6595
            return $toolbar;
6596
        }
6597
6598
        echo $toolbar;
6599
    }
6600
6601
    /**
6602
     * Creates the default learning path folder.
6603
     *
6604
     * @param array $course
6605
     * @param int   $creatorId
6606
     *
6607
     * @return bool
6608
     */
6609
    public static function generate_learning_path_folder($course, $creatorId = 0)
6610
    {
6611
        // Creating learning_path folder
6612
        $dir = 'learning_path';
6613
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6614
        $folder = false;
6615
        $folderData = create_unexisting_directory(
6616
            $course,
6617
            $creatorId,
6618
            0,
6619
            null,
6620
            0,
6621
            '',
6622
            $dir,
6623
            get_lang('Learning paths'),
6624
            0
6625
        );
6626
6627
        if (!empty($folderData)) {
6628
            $folder = true;
6629
        }
6630
6631
        return $folder;
6632
    }
6633
6634
    /**
6635
     * @param array  $course
6636
     * @param string $lp_name
6637
     * @param int    $creatorId
6638
     *
6639
     * @return array
6640
     */
6641
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6642
    {
6643
        $filepath = '';
6644
        $dir = '/learning_path/';
6645
6646
        if (empty($lp_name)) {
6647
            $lp_name = $this->name;
6648
        }
6649
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6650
        $folder = self::generate_learning_path_folder($course, $creatorId);
6651
6652
        // Limits title size
6653
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6654
        $dir = $dir.$title;
6655
6656
        // Creating LP folder
6657
        $documentId = null;
6658
        if ($folder) {
6659
            $folderData = create_unexisting_directory(
6660
                $course,
6661
                $creatorId,
6662
                0,
6663
                0,
6664
                0,
6665
                $filepath,
6666
                $dir,
6667
                $lp_name
6668
            );
6669
            if (!empty($folderData)) {
6670
                $folder = true;
6671
            }
6672
6673
            $documentId = $folderData->getIid();
6674
            $dir = $dir.'/';
6675
            if ($folder) {
6676
                // $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6677
            }
6678
        }
6679
6680
        if (empty($documentId)) {
6681
            $dir = api_remove_trailing_slash($dir);
6682
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6683
        }
6684
6685
        $array = [
6686
            'dir' => $dir,
6687
            'filepath' => $filepath,
6688
            'folder' => $folder,
6689
            'id' => $documentId,
6690
        ];
6691
6692
        return $array;
6693
    }
6694
6695
    /**
6696
     * Create a new document //still needs some finetuning.
6697
     *
6698
     * @param array  $courseInfo
6699
     * @param string $content
6700
     * @param string $title
6701
     * @param string $extension
6702
     * @param int    $parentId
6703
     * @param int    $creatorId  creator id
6704
     *
6705
     * @return int
6706
     */
6707
    public function create_document(
6708
        $courseInfo,
6709
        $content = '',
6710
        $title = '',
6711
        $extension = 'html',
6712
        $parentId = 0,
6713
        $creatorId = 0
6714
    ) {
6715
        if (!empty($courseInfo)) {
6716
            $course_id = $courseInfo['real_id'];
6717
        } else {
6718
            $course_id = api_get_course_int_id();
6719
        }
6720
6721
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6722
        $sessionId = api_get_session_id();
6723
6724
        // Generates folder
6725
        $result = $this->generate_lp_folder($courseInfo);
6726
        $dir = $result['dir'];
6727
6728
        if (empty($parentId) || $parentId == '/') {
6729
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6730
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6731
6732
            if ($parentId === '/') {
6733
                $dir = '/';
6734
            }
6735
6736
            // Please, do not modify this dirname formatting.
6737
            if (strstr($dir, '..')) {
6738
                $dir = '/';
6739
            }
6740
6741
            if (!empty($dir[0]) && $dir[0] == '.') {
6742
                $dir = substr($dir, 1);
6743
            }
6744
            if (!empty($dir[0]) && $dir[0] != '/') {
6745
                $dir = '/'.$dir;
6746
            }
6747
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
6748
                $dir .= '/';
6749
            }
6750
        } else {
6751
            $parentInfo = DocumentManager::get_document_data_by_id(
6752
                $parentId,
6753
                $courseInfo['code']
6754
            );
6755
            if (!empty($parentInfo)) {
6756
                $dir = $parentInfo['path'].'/';
6757
            }
6758
        }
6759
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6760
        // is already escaped twice when it gets here.
6761
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6762
        if (!empty($title)) {
6763
            $title = api_replace_dangerous_char(stripslashes($title));
6764
        } else {
6765
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6766
        }
6767
6768
        $title = disable_dangerous_file($title);
6769
        $filename = $title;
6770
        $content = !empty($content) ? $content : $_POST['content_lp'];
6771
        $tmp_filename = $filename;
6772
        $filename = $tmp_filename.'.'.$extension;
6773
6774
        if ($extension === 'html') {
6775
            $content = stripslashes($content);
6776
            $content = str_replace(
6777
                api_get_path(WEB_COURSE_PATH),
6778
                api_get_path(REL_PATH).'courses/',
6779
                $content
6780
            );
6781
6782
            // Change the path of mp3 to absolute.
6783
            // The first regexp deals with :// urls.
6784
            $content = preg_replace(
6785
                "|(flashvars=\"file=)([^:/]+)/|",
6786
                "$1".api_get_path(
6787
                    REL_COURSE_PATH
6788
                ).$courseInfo['path'].'/document/',
6789
                $content
6790
            );
6791
            // The second regexp deals with audio/ urls.
6792
            $content = preg_replace(
6793
                "|(flashvars=\"file=)([^/]+)/|",
6794
                "$1".api_get_path(
6795
                    REL_COURSE_PATH
6796
                ).$courseInfo['path'].'/document/$2/',
6797
                $content
6798
            );
6799
            // For flv player: To prevent edition problem with firefox,
6800
            // we have to use a strange tip (don't blame me please).
6801
            $content = str_replace(
6802
                '</body>',
6803
                '<style type="text/css">body{}</style></body>',
6804
                $content
6805
            );
6806
        }
6807
6808
        $save_file_path = $dir.$filename;
6809
6810
        $document = DocumentManager::addDocument(
6811
            $courseInfo,
6812
            $save_file_path,
6813
            'file',
6814
            '',
6815
            $tmp_filename,
6816
            '',
6817
            0, //readonly
6818
            true,
6819
            null,
6820
            $sessionId,
6821
            $creatorId,
6822
            false,
6823
            $content,
6824
            $parentId
6825
        );
6826
6827
        $document_id = $document->getIid();
6828
        if ($document_id) {
6829
            $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
6830
            $new_title = $originalTitle;
6831
6832
            if ($new_comment || $new_title) {
6833
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
6834
                $ct = '';
6835
                if ($new_comment) {
6836
                    $ct .= ", comment='".Database::escape_string($new_comment)."'";
6837
                }
6838
                if ($new_title) {
6839
                    $ct .= ", title='".Database::escape_string($new_title)."' ";
6840
                }
6841
6842
                $sql = "UPDATE $tbl_doc SET ".substr($ct, 1)."
6843
                        WHERE c_id = $course_id AND id = $document_id ";
6844
                Database::query($sql);
6845
            }
6846
        }
6847
6848
        return $document_id;
6849
    }
6850
6851
    /**
6852
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
6853
     *
6854
     * @param array $_course array
6855
     */
6856
    public function edit_document($_course)
6857
    {
6858
        $course_id = api_get_course_int_id();
6859
        $urlAppend = '';
6860
        // Please, do not modify this dirname formatting.
6861
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
6862
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
6863
6864
        if (strstr($dir, '..')) {
6865
            $dir = '/';
6866
        }
6867
6868
        if (isset($dir[0]) && $dir[0] == '.') {
6869
            $dir = substr($dir, 1);
6870
        }
6871
6872
        if (isset($dir[0]) && $dir[0] != '/') {
6873
            $dir = '/'.$dir;
6874
        }
6875
6876
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
6877
            $dir .= '/';
6878
        }
6879
6880
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
6881
        if (!is_dir($filepath)) {
6882
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
6883
        }
6884
6885
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
6886
6887
        if (isset($_POST['path']) && !empty($_POST['path'])) {
6888
            $document_id = (int) $_POST['path'];
6889
            $documentInfo = DocumentManager::get_document_data_by_id($document_id, api_get_course_id(), false, null, true);
6890
            if (empty($documentInfo)) {
6891
                // Try with iid
6892
                $table = Database::get_course_table(TABLE_DOCUMENT);
6893
                $sql = "SELECT id, path FROM $table
6894
                        WHERE c_id = $course_id AND iid = $document_id AND path NOT LIKE '%_DELETED_%' ";
6895
                $res_doc = Database::query($sql);
6896
                $row = Database::fetch_array($res_doc);
6897
                if ($row) {
6898
                    $document_id = $row['id'];
6899
                    $documentPath = $row['path'];
6900
                }
6901
            } else {
6902
                $documentPath = $documentInfo['path'];
6903
            }
6904
6905
            $content = stripslashes($_POST['content_lp']);
6906
            $file = $filepath.$documentPath;
6907
6908
            if (!file_exists($file)) {
6909
                return false;
6910
            }
6911
6912
            if ($fp = @fopen($file, 'w')) {
6913
                $content = str_replace(
6914
                    api_get_path(WEB_COURSE_PATH),
6915
                    $urlAppend.api_get_path(REL_COURSE_PATH),
6916
                    $content
6917
                );
6918
                // Change the path of mp3 to absolute.
6919
                // The first regexp deals with :// urls.
6920
                $content = preg_replace(
6921
                    "|(flashvars=\"file=)([^:/]+)/|",
6922
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
6923
                    $content
6924
                );
6925
                // The second regexp deals with audio/ urls.
6926
                $content = preg_replace(
6927
                    "|(flashvars=\"file=)([^:/]+)/|",
6928
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
6929
                    $content
6930
                );
6931
                fputs($fp, $content);
6932
                fclose($fp);
6933
6934
                $sql = "UPDATE $table_doc SET
6935
                            title='".Database::escape_string($_POST['title'])."'
6936
                        WHERE c_id = $course_id AND id = ".$document_id;
6937
                Database::query($sql);
6938
            }
6939
        }
6940
    }
6941
6942
    /**
6943
     * Displays the selected item, with a panel for manipulating the item.
6944
     *
6945
     * @param int    $item_id
6946
     * @param string $msg
6947
     * @param bool   $show_actions
6948
     *
6949
     * @return string
6950
     */
6951
    public function display_item($item_id, $msg = null, $show_actions = true)
6952
    {
6953
        $course_id = api_get_course_int_id();
6954
        $return = '';
6955
        if (is_numeric($item_id)) {
6956
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
6957
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
6958
                    WHERE lp.iid = ".intval($item_id);
6959
            $result = Database::query($sql);
6960
            while ($row = Database::fetch_array($result, 'ASSOC')) {
6961
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
6962
6963
                // Prevents wrong parent selection for document, see Bug#1251.
6964
                if ($row['item_type'] != 'dir') {
6965
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
6966
                }
6967
6968
                if ($show_actions) {
6969
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
6970
                }
6971
                $return .= '<div style="padding:10px;">';
6972
6973
                if ($msg != '') {
6974
                    $return .= $msg;
6975
                }
6976
6977
                $return .= '<h3>'.$row['title'].'</h3>';
6978
6979
                switch ($row['item_type']) {
6980
                    case TOOL_THREAD:
6981
                        $link = $this->rl_get_resource_link_for_learnpath(
6982
                            $course_id,
6983
                            $row['lp_id'],
6984
                            $item_id,
6985
                            0
6986
                        );
6987
                        $return .= Display::url(
6988
                            get_lang('Go to thread'),
6989
                            $link,
6990
                            ['class' => 'btn btn-primary']
6991
                        );
6992
                        break;
6993
                    case TOOL_FORUM:
6994
                        $return .= Display::url(
6995
                            get_lang('Go to the forum'),
6996
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
6997
                            ['class' => 'btn btn-primary']
6998
                        );
6999
                        break;
7000
                    case TOOL_QUIZ:
7001
                        if (!empty($row['path'])) {
7002
                            $exercise = new Exercise();
7003
                            $exercise->read($row['path']);
7004
                            $return .= $exercise->description.'<br />';
7005
                            $return .= Display::url(
7006
                                get_lang('Go to exercise'),
7007
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
7008
                                ['class' => 'btn btn-primary']
7009
                            );
7010
                        }
7011
                        break;
7012
                    case TOOL_LP_FINAL_ITEM:
7013
                        $return .= $this->getSavedFinalItem();
7014
                        break;
7015
                    case TOOL_DOCUMENT:
7016
                    case TOOL_READOUT_TEXT:
7017
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7018
                        $sql_doc = "SELECT path FROM $tbl_doc
7019
                                    WHERE c_id = $course_id AND iid = ".intval($row['path']);
7020
                        $result = Database::query($sql_doc);
7021
                        $path_file = Database::result($result, 0, 0);
7022
                        $path_parts = pathinfo($path_file);
7023
                        // TODO: Correct the following naive comparisons.
7024
                        if (in_array($path_parts['extension'], [
7025
                            'html',
7026
                            'txt',
7027
                            'png',
7028
                            'jpg',
7029
                            'JPG',
7030
                            'jpeg',
7031
                            'JPEG',
7032
                            'gif',
7033
                            'swf',
7034
                            'pdf',
7035
                            'htm',
7036
                        ])) {
7037
                            $return .= $this->display_document($row['path'], true, true);
7038
                        }
7039
                        break;
7040
                    case TOOL_HOTPOTATOES:
7041
                        $return .= $this->display_document($row['path'], false, true);
7042
                        break;
7043
                }
7044
                $return .= '</div>';
7045
            }
7046
        }
7047
7048
        return $return;
7049
    }
7050
7051
    /**
7052
     * Shows the needed forms for editing a specific item.
7053
     *
7054
     * @param int $item_id
7055
     *
7056
     * @throws Exception
7057
     * @throws HTML_QuickForm_Error
7058
     *
7059
     * @return string
7060
     */
7061
    public function display_edit_item($item_id)
7062
    {
7063
        $course_id = api_get_course_int_id();
7064
        $return = '';
7065
        $item_id = (int) $item_id;
7066
7067
        if (empty($item_id)) {
7068
            return '';
7069
        }
7070
7071
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7072
        $sql = "SELECT * FROM $tbl_lp_item
7073
                WHERE iid = ".$item_id;
7074
        $res = Database::query($sql);
7075
        $row = Database::fetch_array($res);
7076
        switch ($row['item_type']) {
7077
            case 'dir':
7078
            case 'asset':
7079
            case 'sco':
7080
                if (isset($_GET['view']) && $_GET['view'] == 'build') {
7081
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7082
                    $return .= $this->display_item_form(
7083
                        $row['item_type'],
7084
                        get_lang('Edit the current section').' :',
7085
                        'edit',
7086
                        $item_id,
7087
                        $row
7088
                    );
7089
                } else {
7090
                    $return .= $this->display_item_form(
7091
                        $row['item_type'],
7092
                        get_lang('Edit the current section').' :',
7093
                        'edit_item',
7094
                        $item_id,
7095
                        $row
7096
                    );
7097
                }
7098
                break;
7099
            case TOOL_DOCUMENT:
7100
            case TOOL_READOUT_TEXT:
7101
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7102
                $sql = "SELECT lp.*, doc.path as dir
7103
                        FROM $tbl_lp_item as lp
7104
                        LEFT JOIN $tbl_doc as doc
7105
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7106
                        WHERE
7107
                            doc.c_id = $course_id AND
7108
                            lp.iid = ".$item_id;
7109
                $res_step = Database::query($sql);
7110
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7111
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7112
7113
                if ($row['item_type'] === TOOL_DOCUMENT) {
7114
                    $return .= $this->display_document_form('edit', $item_id, $row_step);
7115
                }
7116
7117
                if ($row['item_type'] === TOOL_READOUT_TEXT) {
7118
                    $return .= $this->displayFrmReadOutText('edit', $item_id, $row_step);
7119
                }
7120
                break;
7121
            case TOOL_LINK:
7122
                $linkId = (int) $row['path'];
7123
                if (!empty($linkId)) {
7124
                    $table = Database::get_course_table(TABLE_LINK);
7125
                    $sql = 'SELECT url FROM '.$table.'
7126
                            WHERE c_id = '.$course_id.' AND iid = '.$linkId;
7127
                    $res_link = Database::query($sql);
7128
                    $row_link = Database::fetch_array($res_link);
7129
                    if (empty($row_link)) {
7130
                        // Try with id
7131
                        $sql = 'SELECT url FROM '.$table.'
7132
                                WHERE c_id = '.$course_id.' AND id = '.$linkId;
7133
                        $res_link = Database::query($sql);
7134
                        $row_link = Database::fetch_array($res_link);
7135
                    }
7136
7137
                    if (is_array($row_link)) {
7138
                        $row['url'] = $row_link['url'];
7139
                    }
7140
                }
7141
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7142
                $return .= $this->display_link_form('edit', $item_id, $row);
7143
                break;
7144
            case TOOL_LP_FINAL_ITEM:
7145
                Session::write('finalItem', true);
7146
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7147
                $sql = "SELECT lp.*, doc.path as dir
7148
                        FROM $tbl_lp_item as lp
7149
                        LEFT JOIN $tbl_doc as doc
7150
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7151
                        WHERE
7152
                            doc.c_id = $course_id AND
7153
                            lp.iid = ".$item_id;
7154
                $res_step = Database::query($sql);
7155
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7156
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7157
                $return .= $this->display_document_form('edit', $item_id, $row_step);
7158
                break;
7159
            case TOOL_QUIZ:
7160
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7161
                $return .= $this->display_quiz_form('edit', $item_id, $row);
7162
                break;
7163
            case TOOL_HOTPOTATOES:
7164
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7165
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7166
                break;
7167
            case TOOL_STUDENTPUBLICATION:
7168
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7169
                $return .= $this->display_student_publication_form('edit', $item_id, $row);
7170
                break;
7171
            case TOOL_FORUM:
7172
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7173
                $return .= $this->display_forum_form('edit', $item_id, $row);
7174
                break;
7175
            case TOOL_THREAD:
7176
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7177
                $return .= $this->display_thread_form('edit', $item_id, $row);
7178
                break;
7179
        }
7180
7181
        return $return;
7182
    }
7183
7184
    /**
7185
     * Function that displays a list with al the resources that
7186
     * could be added to the learning path.
7187
     *
7188
     * @throws Exception
7189
     * @throws HTML_QuickForm_Error
7190
     *
7191
     * @return bool
7192
     */
7193
    public function display_resources()
7194
    {
7195
        $course_code = api_get_course_id();
7196
7197
        // Get all the docs.
7198
        $documents = $this->get_documents(true);
7199
7200
        // Get all the exercises.
7201
        $exercises = $this->get_exercises();
7202
7203
        // Get all the links.
7204
        $links = $this->get_links();
7205
7206
        // Get all the student publications.
7207
        $works = $this->get_student_publications();
7208
7209
        // Get all the forums.
7210
        $forums = $this->get_forums(null, $course_code);
7211
7212
        // Get the final item form (see BT#11048) .
7213
        $finish = $this->getFinalItemForm();
7214
7215
        $headers = [
7216
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7217
            Display::return_icon('quiz.png', get_lang('Tests'), [], ICON_SIZE_BIG),
7218
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7219
            Display::return_icon('works.png', get_lang('Assignments'), [], ICON_SIZE_BIG),
7220
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7221
            Display::return_icon('add_learnpath_section.png', get_lang('Add section'), [], ICON_SIZE_BIG),
7222
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7223
        ];
7224
7225
        echo Display::return_message(get_lang('Click on the [Learner view] button to see your learning path'), 'normal');
7226
        $dir = $this->display_item_form('dir', get_lang('EnterDataAdd section'), 'add_item');
7227
7228
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
7229
7230
        echo Display::tabs(
7231
            $headers,
7232
            [
7233
                $documents,
7234
                $exercises,
7235
                $links,
7236
                $works,
7237
                $forums,
7238
                $dir,
7239
                $finish,
7240
            ],
7241
            'resource_tab',
7242
            [],
7243
            [],
7244
            $selected
7245
        );
7246
7247
        return true;
7248
    }
7249
7250
    /**
7251
     * Returns the extension of a document.
7252
     *
7253
     * @param string $filename
7254
     *
7255
     * @return string Extension (part after the last dot)
7256
     */
7257
    public function get_extension($filename)
7258
    {
7259
        $explode = explode('.', $filename);
7260
7261
        return $explode[count($explode) - 1];
7262
    }
7263
7264
    /**
7265
     * Displays a document by id.
7266
     *
7267
     * @param int  $id
7268
     * @param bool $show_title
7269
     * @param bool $iframe
7270
     * @param bool $edit_link
7271
     *
7272
     * @return string
7273
     */
7274
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7275
    {
7276
        $_course = api_get_course_info();
7277
        $course_id = api_get_course_int_id();
7278
        $id = (int) $id;
7279
        $return = '';
7280
        $table = Database::get_course_table(TABLE_DOCUMENT);
7281
        $sql_doc = "SELECT * FROM $table
7282
                    WHERE c_id = $course_id AND iid = $id";
7283
        $res_doc = Database::query($sql_doc);
7284
        $row_doc = Database::fetch_array($res_doc);
7285
7286
        // TODO: Add a path filter.
7287
        if ($iframe) {
7288
            $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>';
7289
        } else {
7290
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7291
        }
7292
7293
        return $return;
7294
    }
7295
7296
    /**
7297
     * Return HTML form to add/edit a quiz.
7298
     *
7299
     * @param string $action     Action (add/edit)
7300
     * @param int    $id         Item ID if already exists
7301
     * @param mixed  $extra_info Extra information (quiz ID if integer)
7302
     *
7303
     * @throws Exception
7304
     *
7305
     * @return string HTML form
7306
     */
7307
    public function display_quiz_form($action = 'add', $id = 0, $extra_info = '')
7308
    {
7309
        $course_id = api_get_course_int_id();
7310
        $id = (int) $id;
7311
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7312
        $item_title = '';
7313
        $item_description = '';
7314
        if ($id != 0 && is_array($extra_info)) {
7315
            $item_title = $extra_info['title'];
7316
            $item_description = $extra_info['description'];
7317
        } elseif (is_numeric($extra_info)) {
7318
            $sql = "SELECT title, description
7319
                    FROM $tbl_quiz
7320
                    WHERE c_id = $course_id AND iid = ".$extra_info;
7321
7322
            $result = Database::query($sql);
7323
            $row = Database::fetch_array($result);
7324
            $item_title = $row['title'];
7325
            $item_description = $row['description'];
7326
        }
7327
        $item_title = Security::remove_XSS($item_title);
7328
        $item_description = Security::remove_XSS($item_description);
7329
7330
        $parent = 0;
7331
        if ($id != 0 && is_array($extra_info)) {
7332
            $parent = $extra_info['parent_item_id'];
7333
        }
7334
7335
        $arrLP = $this->getItemsForForm();
7336
        $this->tree_array($arrLP);
7337
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7338
        unset($this->arrMenu);
7339
7340
        $form = new FormValidator(
7341
            'quiz_form',
7342
            'POST',
7343
            $this->getCurrentBuildingModeURL()
7344
        );
7345
        $defaults = [];
7346
7347
        if ($action === 'add') {
7348
            $legend = get_lang('Adding a test to the course');
7349
        } elseif ($action === 'move') {
7350
            $legend = get_lang('Move the current test');
7351
        } else {
7352
            $legend = get_lang('Edit the current test');
7353
        }
7354
7355
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7356
            $legend .= Display::return_message(get_lang('Warning ! ! !').' ! '.get_lang('Warning ! ! !EditingDocument'));
7357
        }
7358
7359
        $form->addHeader($legend);
7360
7361
        if ($action != 'move') {
7362
            $this->setItemTitle($form);
7363
            $defaults['title'] = $item_title;
7364
        }
7365
7366
        // Select for Parent item, root or chapter
7367
        $selectParent = $form->addSelect(
7368
            'parent',
7369
            get_lang('Parent'),
7370
            [],
7371
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7372
        );
7373
        $selectParent->addOption($this->name, 0);
7374
7375
        $arrHide = [
7376
            $id,
7377
        ];
7378
        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...
7379
            if ($action != 'add') {
7380
                if (
7381
                    ($arrLP[$i]['item_type'] == 'dir') &&
7382
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7383
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7384
                ) {
7385
                    $selectParent->addOption(
7386
                        $arrLP[$i]['title'],
7387
                        $arrLP[$i]['id'],
7388
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7389
                    );
7390
7391
                    if ($parent == $arrLP[$i]['id']) {
7392
                        $selectParent->setSelected($arrLP[$i]['id']);
7393
                    }
7394
                } else {
7395
                    $arrHide[] = $arrLP[$i]['id'];
7396
                }
7397
            } else {
7398
                if ($arrLP[$i]['item_type'] == 'dir') {
7399
                    $selectParent->addOption(
7400
                        $arrLP[$i]['title'],
7401
                        $arrLP[$i]['id'],
7402
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7403
                    );
7404
7405
                    if ($parent == $arrLP[$i]['id']) {
7406
                        $selectParent->setSelected($arrLP[$i]['id']);
7407
                    }
7408
                }
7409
            }
7410
        }
7411
7412
        if (is_array($arrLP)) {
7413
            reset($arrLP);
7414
        }
7415
7416
        $selectPrevious = $form->addSelect(
7417
            'previous',
7418
            get_lang('Position'),
7419
            [],
7420
            ['id' => 'previous']
7421
        );
7422
        $selectPrevious->addOption(get_lang('First position'), 0);
7423
7424
        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...
7425
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7426
                $arrLP[$i]['id'] != $id
7427
            ) {
7428
                $selectPrevious->addOption(
7429
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7430
                    $arrLP[$i]['id']
7431
                );
7432
7433
                if (is_array($extra_info)) {
7434
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7435
                        $selectPrevious->setSelected($arrLP[$i]['id']);
7436
                    }
7437
                } elseif ($action == 'add') {
7438
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7439
                }
7440
            }
7441
        }
7442
7443
        if ($action != 'move') {
7444
            $arrHide = [];
7445
            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...
7446
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7447
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7448
                }
7449
            }
7450
        }
7451
7452
        if ($action === 'add') {
7453
            $form->addButtonSave(get_lang('Add test to course'), 'submit_button');
7454
        } else {
7455
            $form->addButtonSave(get_lang('Edit the current test'), 'submit_button');
7456
        }
7457
7458
        if ($action === 'move') {
7459
            $form->addHidden('title', $item_title);
7460
            $form->addHidden('description', $item_description);
7461
        }
7462
7463
        if (is_numeric($extra_info)) {
7464
            $form->addHidden('path', $extra_info);
7465
        } elseif (is_array($extra_info)) {
7466
            $form->addHidden('path', $extra_info['path']);
7467
        }
7468
7469
        $form->addHidden('type', TOOL_QUIZ);
7470
        $form->addHidden('post_time', time());
7471
        $form->setDefaults($defaults);
7472
7473
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7474
    }
7475
7476
    /**
7477
     * Addition of Hotpotatoes tests.
7478
     *
7479
     * @param string $action
7480
     * @param int    $id         Internal ID of the item
7481
     * @param string $extra_info
7482
     *
7483
     * @return string HTML structure to display the hotpotatoes addition formular
7484
     */
7485
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
7486
    {
7487
        $course_id = api_get_course_int_id();
7488
        $uploadPath = DIR_HOTPOTATOES;
7489
7490
        if ($id != 0 && is_array($extra_info)) {
7491
            $item_title = stripslashes($extra_info['title']);
7492
            $item_description = stripslashes($extra_info['description']);
7493
        } elseif (is_numeric($extra_info)) {
7494
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
7495
7496
            $sql = "SELECT * FROM $TBL_DOCUMENT
7497
                    WHERE
7498
                        c_id = $course_id AND
7499
                        path LIKE '".$uploadPath."/%/%htm%' AND
7500
                        iid = ".(int) $extra_info."
7501
                    ORDER BY iid ASC";
7502
7503
            $res_hot = Database::query($sql);
7504
            $row = Database::fetch_array($res_hot);
7505
7506
            $item_title = $row['title'];
7507
            $item_description = $row['description'];
7508
7509
            if (!empty($row['comment'])) {
7510
                $item_title = $row['comment'];
7511
            }
7512
        } else {
7513
            $item_title = '';
7514
            $item_description = '';
7515
        }
7516
7517
        $parent = 0;
7518
        if ($id != 0 && is_array($extra_info)) {
7519
            $parent = $extra_info['parent_item_id'];
7520
        }
7521
7522
        $arrLP = $this->getItemsForForm();
7523
        $legend = '<legend>';
7524
        if ($action == 'add') {
7525
            $legend .= get_lang('Adding a test to the course');
7526
        } elseif ($action == 'move') {
7527
            $legend .= get_lang('Move the current test');
7528
        } else {
7529
            $legend .= get_lang('Edit the current test');
7530
        }
7531
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7532
            $legend .= Display:: return_message(
7533
                get_lang('Warning ! ! !').' ! '.get_lang('Warning ! ! !EditingDocument')
7534
            );
7535
        }
7536
        $legend .= '</legend>';
7537
7538
        $return = '<form method="POST">';
7539
        $return .= $legend;
7540
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
7541
        $return .= '<tr>';
7542
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
7543
        $return .= '<td class="input">';
7544
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
7545
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
7546
        $arrHide = [$id];
7547
7548
        if (count($arrLP) > 0) {
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 ($action != 'add') {
7551
                    if ($arrLP[$i]['item_type'] == 'dir' &&
7552
                        !in_array($arrLP[$i]['id'], $arrHide) &&
7553
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7554
                    ) {
7555
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7556
                    } else {
7557
                        $arrHide[] = $arrLP[$i]['id'];
7558
                    }
7559
                } else {
7560
                    if ($arrLP[$i]['item_type'] == 'dir') {
7561
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7562
                    }
7563
                }
7564
            }
7565
            reset($arrLP);
7566
        }
7567
7568
        $return .= '</select>';
7569
        $return .= '</td>';
7570
        $return .= '</tr>';
7571
        $return .= '<tr>';
7572
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
7573
        $return .= '<td class="input">';
7574
        $return .= '<select id="previous" name="previous" size="1">';
7575
        $return .= '<option class="top" value="0">'.get_lang('First position').'</option>';
7576
7577
        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...
7578
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7579
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7580
                    $selected = 'selected="selected" ';
7581
                } elseif ($action == 'add') {
7582
                    $selected = 'selected="selected" ';
7583
                } else {
7584
                    $selected = '';
7585
                }
7586
7587
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.
7588
                    get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
7589
            }
7590
        }
7591
7592
        $return .= '</select>';
7593
        $return .= '</td>';
7594
        $return .= '</tr>';
7595
7596
        if ($action != 'move') {
7597
            $return .= '<tr>';
7598
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
7599
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
7600
            $return .= '</tr>';
7601
            $id_prerequisite = 0;
7602
            if (is_array($arrLP) && count($arrLP) > 0) {
7603
                foreach ($arrLP as $key => $value) {
7604
                    if ($value['id'] == $id) {
7605
                        $id_prerequisite = $value['prerequisite'];
7606
                        break;
7607
                    }
7608
                }
7609
7610
                $arrHide = [];
7611
                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...
7612
                    if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7613
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7614
                    }
7615
                }
7616
            }
7617
        }
7618
7619
        $return .= '<tr>';
7620
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.
7621
            get_lang('Save hotpotatoes').'</button></td>';
7622
        $return .= '</tr>';
7623
        $return .= '</table>';
7624
7625
        if ($action == 'move') {
7626
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
7627
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
7628
        }
7629
7630
        if (is_numeric($extra_info)) {
7631
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
7632
        } elseif (is_array($extra_info)) {
7633
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
7634
        }
7635
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
7636
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
7637
        $return .= '</form>';
7638
7639
        return $return;
7640
    }
7641
7642
    /**
7643
     * Return the form to display the forum edit/add option.
7644
     *
7645
     * @param string $action
7646
     * @param int    $id         ID of the lp_item if already exists
7647
     * @param string $extra_info
7648
     *
7649
     * @throws Exception
7650
     *
7651
     * @return string HTML form
7652
     */
7653
    public function display_forum_form($action = 'add', $id = 0, $extra_info = '')
7654
    {
7655
        $course_id = api_get_course_int_id();
7656
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
7657
7658
        $item_title = '';
7659
        $item_description = '';
7660
7661
        if ($id != 0 && is_array($extra_info)) {
7662
            $item_title = stripslashes($extra_info['title']);
7663
        } elseif (is_numeric($extra_info)) {
7664
            $sql = "SELECT forum_title as title, forum_comment as comment
7665
                    FROM $tbl_forum
7666
                    WHERE c_id = $course_id AND forum_id = ".$extra_info;
7667
7668
            $result = Database::query($sql);
7669
            $row = Database::fetch_array($result);
7670
7671
            $item_title = $row['title'];
7672
            $item_description = $row['comment'];
7673
        }
7674
        $parent = 0;
7675
        if ($id != 0 && is_array($extra_info)) {
7676
            $parent = $extra_info['parent_item_id'];
7677
        }
7678
        $arrLP = $this->getItemsForForm();
7679
        $this->tree_array($arrLP);
7680
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7681
        unset($this->arrMenu);
7682
7683
        if ($action == 'add') {
7684
            $legend = get_lang('Adding a forum to the course');
7685
        } elseif ($action == 'move') {
7686
            $legend = get_lang('Move the current forum');
7687
        } else {
7688
            $legend = get_lang('Edit the current forum');
7689
        }
7690
7691
        $form = new FormValidator(
7692
            'forum_form',
7693
            'POST',
7694
            $this->getCurrentBuildingModeURL()
7695
        );
7696
        $defaults = [];
7697
7698
        $form->addHeader($legend);
7699
7700
        if ($action != 'move') {
7701
            $this->setItemTitle($form);
7702
            $defaults['title'] = $item_title;
7703
        }
7704
7705
        $selectParent = $form->addSelect(
7706
            'parent',
7707
            get_lang('Parent'),
7708
            [],
7709
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
7710
        );
7711
        $selectParent->addOption($this->name, 0);
7712
        $arrHide = [
7713
            $id,
7714
        ];
7715
        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...
7716
            if ($action != 'add') {
7717
                if ($arrLP[$i]['item_type'] == 'dir' &&
7718
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7719
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7720
                ) {
7721
                    $selectParent->addOption(
7722
                        $arrLP[$i]['title'],
7723
                        $arrLP[$i]['id'],
7724
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7725
                    );
7726
7727
                    if ($parent == $arrLP[$i]['id']) {
7728
                        $selectParent->setSelected($arrLP[$i]['id']);
7729
                    }
7730
                } else {
7731
                    $arrHide[] = $arrLP[$i]['id'];
7732
                }
7733
            } else {
7734
                if ($arrLP[$i]['item_type'] == 'dir') {
7735
                    $selectParent->addOption(
7736
                        $arrLP[$i]['title'],
7737
                        $arrLP[$i]['id'],
7738
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7739
                    );
7740
7741
                    if ($parent == $arrLP[$i]['id']) {
7742
                        $selectParent->setSelected($arrLP[$i]['id']);
7743
                    }
7744
                }
7745
            }
7746
        }
7747
7748
        if (is_array($arrLP)) {
7749
            reset($arrLP);
7750
        }
7751
7752
        $selectPrevious = $form->addSelect(
7753
            'previous',
7754
            get_lang('Position'),
7755
            [],
7756
            ['id' => 'previous', 'class' => 'learnpath_item_form']
7757
        );
7758
        $selectPrevious->addOption(get_lang('First position'), 0);
7759
7760
        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...
7761
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7762
                $arrLP[$i]['id'] != $id
7763
            ) {
7764
                $selectPrevious->addOption(
7765
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7766
                    $arrLP[$i]['id']
7767
                );
7768
7769
                if (isset($extra_info['previous_item_id']) &&
7770
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
7771
                ) {
7772
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7773
                } elseif ($action == 'add') {
7774
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7775
                }
7776
            }
7777
        }
7778
7779
        if ($action != 'move') {
7780
            $id_prerequisite = 0;
7781
            if (is_array($arrLP)) {
7782
                foreach ($arrLP as $key => $value) {
7783
                    if ($value['id'] == $id) {
7784
                        $id_prerequisite = $value['prerequisite'];
7785
                        break;
7786
                    }
7787
                }
7788
            }
7789
7790
            $arrHide = [];
7791
            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...
7792
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7793
                    if (isset($extra_info['previous_item_id']) &&
7794
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
7795
                    ) {
7796
                        $s_selected_position = $arrLP[$i]['id'];
7797
                    } elseif ($action == 'add') {
7798
                        $s_selected_position = 0;
7799
                    }
7800
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7801
                }
7802
            }
7803
        }
7804
7805
        if ($action == 'add') {
7806
            $form->addButtonSave(get_lang('Add forum to course'), 'submit_button');
7807
        } else {
7808
            $form->addButtonSave(get_lang('Edit the current forum'), 'submit_button');
7809
        }
7810
7811
        if ($action == 'move') {
7812
            $form->addHidden('title', $item_title);
7813
            $form->addHidden('description', $item_description);
7814
        }
7815
7816
        if (is_numeric($extra_info)) {
7817
            $form->addHidden('path', $extra_info);
7818
        } elseif (is_array($extra_info)) {
7819
            $form->addHidden('path', $extra_info['path']);
7820
        }
7821
        $form->addHidden('type', TOOL_FORUM);
7822
        $form->addHidden('post_time', time());
7823
        $form->setDefaults($defaults);
7824
7825
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7826
    }
7827
7828
    /**
7829
     * Return HTML form to add/edit forum threads.
7830
     *
7831
     * @param string $action
7832
     * @param int    $id         Item ID if already exists in learning path
7833
     * @param string $extra_info
7834
     *
7835
     * @throws Exception
7836
     *
7837
     * @return string HTML form
7838
     */
7839
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
7840
    {
7841
        $course_id = api_get_course_int_id();
7842
        if (empty($course_id)) {
7843
            return null;
7844
        }
7845
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
7846
7847
        $item_title = '';
7848
        $item_description = '';
7849
        if ($id != 0 && is_array($extra_info)) {
7850
            $item_title = stripslashes($extra_info['title']);
7851
        } elseif (is_numeric($extra_info)) {
7852
            $sql = "SELECT thread_title as title FROM $tbl_forum
7853
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
7854
7855
            $result = Database::query($sql);
7856
            $row = Database::fetch_array($result);
7857
7858
            $item_title = $row['title'];
7859
            $item_description = '';
7860
        }
7861
7862
        $parent = 0;
7863
        if ($id != 0 && is_array($extra_info)) {
7864
            $parent = $extra_info['parent_item_id'];
7865
        }
7866
7867
        $arrLP = $this->getItemsForForm();
7868
        $this->tree_array($arrLP);
7869
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7870
        unset($this->arrMenu);
7871
7872
        $form = new FormValidator(
7873
            'thread_form',
7874
            'POST',
7875
            $this->getCurrentBuildingModeURL()
7876
        );
7877
        $defaults = [];
7878
7879
        if ($action == 'add') {
7880
            $legend = get_lang('Adding a forum to the course');
7881
        } elseif ($action == 'move') {
7882
            $legend = get_lang('Move the current forum');
7883
        } else {
7884
            $legend = get_lang('Edit the current forum');
7885
        }
7886
7887
        $form->addHeader($legend);
7888
        $selectParent = $form->addSelect(
7889
            'parent',
7890
            get_lang('Parent'),
7891
            [],
7892
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7893
        );
7894
        $selectParent->addOption($this->name, 0);
7895
7896
        $arrHide = [
7897
            $id,
7898
        ];
7899
7900
        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...
7901
            if ($action != 'add') {
7902
                if (
7903
                    ($arrLP[$i]['item_type'] == 'dir') &&
7904
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7905
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7906
                ) {
7907
                    $selectParent->addOption(
7908
                        $arrLP[$i]['title'],
7909
                        $arrLP[$i]['id'],
7910
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7911
                    );
7912
7913
                    if ($parent == $arrLP[$i]['id']) {
7914
                        $selectParent->setSelected($arrLP[$i]['id']);
7915
                    }
7916
                } else {
7917
                    $arrHide[] = $arrLP[$i]['id'];
7918
                }
7919
            } else {
7920
                if ($arrLP[$i]['item_type'] == 'dir') {
7921
                    $selectParent->addOption(
7922
                        $arrLP[$i]['title'],
7923
                        $arrLP[$i]['id'],
7924
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7925
                    );
7926
7927
                    if ($parent == $arrLP[$i]['id']) {
7928
                        $selectParent->setSelected($arrLP[$i]['id']);
7929
                    }
7930
                }
7931
            }
7932
        }
7933
7934
        if ($arrLP != null) {
7935
            reset($arrLP);
7936
        }
7937
7938
        $selectPrevious = $form->addSelect(
7939
            'previous',
7940
            get_lang('Position'),
7941
            [],
7942
            ['id' => 'previous']
7943
        );
7944
        $selectPrevious->addOption(get_lang('First position'), 0);
7945
7946
        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...
7947
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7948
                $selectPrevious->addOption(
7949
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7950
                    $arrLP[$i]['id']
7951
                );
7952
7953
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7954
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7955
                } elseif ($action == 'add') {
7956
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7957
                }
7958
            }
7959
        }
7960
7961
        if ($action != 'move') {
7962
            $this->setItemTitle($form);
7963
            $defaults['title'] = $item_title;
7964
7965
            $id_prerequisite = 0;
7966
            if ($arrLP != null) {
7967
                foreach ($arrLP as $key => $value) {
7968
                    if ($value['id'] == $id) {
7969
                        $id_prerequisite = $value['prerequisite'];
7970
                        break;
7971
                    }
7972
                }
7973
            }
7974
7975
            $arrHide = [];
7976
            $s_selected_position = 0;
7977
            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...
7978
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7979
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7980
                        $s_selected_position = $arrLP[$i]['id'];
7981
                    } elseif ($action == 'add') {
7982
                        $s_selected_position = 0;
7983
                    }
7984
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7985
                }
7986
            }
7987
7988
            $selectPrerequisites = $form->addSelect(
7989
                'prerequisites',
7990
                get_lang('Prerequisites'),
7991
                [],
7992
                ['id' => 'prerequisites']
7993
            );
7994
            $selectPrerequisites->addOption(get_lang('No prerequisites'), 0);
7995
7996
            foreach ($arrHide as $key => $value) {
7997
                $selectPrerequisites->addOption($value['value'], $key);
7998
7999
                if ($key == $s_selected_position && $action == 'add') {
8000
                    $selectPrerequisites->setSelected($key);
8001
                } elseif ($key == $id_prerequisite && $action == 'edit') {
8002
                    $selectPrerequisites->setSelected($key);
8003
                }
8004
            }
8005
        }
8006
8007
        $form->addButtonSave(get_lang('Validate'), 'submit_button');
8008
8009
        if ($action == 'move') {
8010
            $form->addHidden('title', $item_title);
8011
            $form->addHidden('description', $item_description);
8012
        }
8013
8014
        if (is_numeric($extra_info)) {
8015
            $form->addHidden('path', $extra_info);
8016
        } elseif (is_array($extra_info)) {
8017
            $form->addHidden('path', $extra_info['path']);
8018
        }
8019
8020
        $form->addHidden('type', TOOL_THREAD);
8021
        $form->addHidden('post_time', time());
8022
        $form->setDefaults($defaults);
8023
8024
        return $form->returnForm();
8025
    }
8026
8027
    /**
8028
     * Return the HTML form to display an item (generally a dir item).
8029
     *
8030
     * @param string $item_type
8031
     * @param string $title
8032
     * @param string $action
8033
     * @param int    $id
8034
     * @param string $extra_info
8035
     *
8036
     * @throws Exception
8037
     * @throws HTML_QuickForm_Error
8038
     *
8039
     * @return string HTML form
8040
     */
8041
    public function display_item_form(
8042
        $item_type,
8043
        $title = '',
8044
        $action = 'add_item',
8045
        $id = 0,
8046
        $extra_info = 'new'
8047
    ) {
8048
        $_course = api_get_course_info();
8049
8050
        global $charset;
8051
8052
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8053
        $item_title = '';
8054
        $item_description = '';
8055
        $item_path_fck = '';
8056
8057
        if ($id != 0 && is_array($extra_info)) {
8058
            $item_title = $extra_info['title'];
8059
            $item_description = $extra_info['description'];
8060
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8061
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8062
        }
8063
        $parent = 0;
8064
        if ($id != 0 && is_array($extra_info)) {
8065
            $parent = $extra_info['parent_item_id'];
8066
        }
8067
8068
        $id = (int) $id;
8069
        $sql = "SELECT * FROM $tbl_lp_item
8070
                WHERE
8071
                    lp_id = ".$this->lp_id." AND
8072
                    iid != $id";
8073
8074
        if ($item_type == 'dir') {
8075
            $sql .= " AND parent_item_id = 0";
8076
        }
8077
8078
        $result = Database::query($sql);
8079
        $arrLP = [];
8080
        while ($row = Database::fetch_array($result)) {
8081
            $arrLP[] = [
8082
                'id' => $row['iid'],
8083
                'item_type' => $row['item_type'],
8084
                'title' => $this->cleanItemTitle($row['title']),
8085
                'title_raw' => $row['title'],
8086
                'path' => $row['path'],
8087
                'description' => $row['description'],
8088
                'parent_item_id' => $row['parent_item_id'],
8089
                'previous_item_id' => $row['previous_item_id'],
8090
                'next_item_id' => $row['next_item_id'],
8091
                'max_score' => $row['max_score'],
8092
                'min_score' => $row['min_score'],
8093
                'mastery_score' => $row['mastery_score'],
8094
                'prerequisite' => $row['prerequisite'],
8095
                'display_order' => $row['display_order'],
8096
            ];
8097
        }
8098
8099
        $this->tree_array($arrLP);
8100
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8101
        unset($this->arrMenu);
8102
8103
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8104
8105
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
8106
        $defaults['title'] = api_html_entity_decode(
8107
            $item_title,
8108
            ENT_QUOTES,
8109
            $charset
8110
        );
8111
        $defaults['description'] = $item_description;
8112
8113
        $form->addHeader($title);
8114
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
8115
        $arrHide[0]['padding'] = 20;
8116
        $charset = api_get_system_encoding();
8117
        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...
8118
            if ($action != 'add') {
8119
                if ($arrLP[$i]['item_type'] === 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8120
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8121
                ) {
8122
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8123
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8124
                    if ($parent == $arrLP[$i]['id']) {
8125
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8126
                    }
8127
                }
8128
            } else {
8129
                if ($arrLP[$i]['item_type'] === 'dir') {
8130
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8131
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8132
                    if ($parent == $arrLP[$i]['id']) {
8133
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8134
                    }
8135
                }
8136
            }
8137
        }
8138
8139
        if ($action != 'move') {
8140
            $this->setItemTitle($form);
8141
        } else {
8142
            $form->addElement('hidden', 'title');
8143
        }
8144
8145
        $parentSelect = $form->addElement(
8146
            'select',
8147
            'parent',
8148
            get_lang('Parent'),
8149
            '',
8150
            [
8151
                'id' => 'idParent',
8152
                'onchange' => 'javascript: load_cbo(this.value);',
8153
            ]
8154
        );
8155
8156
        foreach ($arrHide as $key => $value) {
8157
            $parentSelect->addOption(
8158
                $value['value'],
8159
                $key,
8160
                'style="padding-left:'.$value['padding'].'px;"'
8161
            );
8162
            $lastPosition = $key;
8163
        }
8164
8165
        if (!empty($s_selected_parent)) {
8166
            $parentSelect->setSelected($s_selected_parent);
8167
        }
8168
8169
        if (is_array($arrLP)) {
8170
            reset($arrLP);
8171
        }
8172
8173
        $arrHide = [];
8174
        // POSITION
8175
        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...
8176
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id &&
8177
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
8178
                //this is the same!
8179
                if (isset($extra_info['previous_item_id']) &&
8180
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8181
                ) {
8182
                    $s_selected_position = $arrLP[$i]['id'];
8183
                } elseif ($action == 'add') {
8184
                    $s_selected_position = $arrLP[$i]['id'];
8185
                }
8186
8187
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8188
            }
8189
        }
8190
8191
        $position = $form->addElement(
8192
            'select',
8193
            'previous',
8194
            get_lang('Position'),
8195
            '',
8196
            ['id' => 'previous']
8197
        );
8198
        $padding = isset($value['padding']) ? $value['padding'] : 0;
8199
        $position->addOption(get_lang('First position'), 0, 'style="padding-left:'.$padding.'px;"');
8200
8201
        $lastPosition = null;
8202
        foreach ($arrHide as $key => $value) {
8203
            $position->addOption($value['value'], $key, 'style="padding-left:'.$padding.'px;"');
8204
            $lastPosition = $key;
8205
        }
8206
8207
        if (!empty($s_selected_position)) {
8208
            $position->setSelected($s_selected_position);
8209
        }
8210
8211
        // When new chapter add at the end
8212
        if ($action === 'add_item') {
8213
            $position->setSelected($lastPosition);
8214
        }
8215
8216
        if (is_array($arrLP)) {
8217
            reset($arrLP);
8218
        }
8219
8220
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
8221
8222
        //fix in order to use the tab
8223
        if ($item_type === 'dir') {
8224
            $form->addElement('hidden', 'type', 'dir');
8225
        }
8226
8227
        $extension = null;
8228
        if (!empty($item_path)) {
8229
            $extension = pathinfo($item_path, PATHINFO_EXTENSION);
8230
        }
8231
8232
        //assets can't be modified
8233
        //$item_type == 'asset' ||
8234
        if (($item_type == 'sco') && ($extension == 'html' || $extension == 'htm')) {
8235
            if ($item_type == 'sco') {
8236
                $form->addElement(
8237
                    'html',
8238
                    '<script>alert("'.get_lang('Warning ! ! !WhenEditingScorm').'")</script>'
8239
                );
8240
            }
8241
            $renderer = $form->defaultRenderer();
8242
            $renderer->setElementTemplate(
8243
                '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
8244
                'content_lp'
8245
            );
8246
8247
            $relative_prefix = '';
8248
            $editor_config = [
8249
                'ToolbarSet' => 'LearningPathDocuments',
8250
                'Width' => '100%',
8251
                'Height' => '500',
8252
                'FullPage' => true,
8253
                'CreateDocumentDir' => $relative_prefix,
8254
                'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
8255
                'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
8256
            ];
8257
8258
            $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
8259
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
8260
            $defaults['content_lp'] = file_get_contents($content_path);
8261
        }
8262
8263
        if (!empty($id)) {
8264
            $form->addHidden('id', $id);
8265
        }
8266
8267
        $form->addElement('hidden', 'type', $item_type);
8268
        $form->addElement('hidden', 'post_time', time());
8269
        $form->setDefaults($defaults);
8270
8271
        return $form->returnForm();
8272
    }
8273
8274
    /**
8275
     * @return string
8276
     */
8277
    public function getCurrentBuildingModeURL()
8278
    {
8279
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
8280
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
8281
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
8282
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
8283
8284
        $currentUrl = api_get_self().'?'.api_get_cidreq().
8285
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
8286
8287
        return $currentUrl;
8288
    }
8289
8290
    /**
8291
     * Returns the form to update or create a document.
8292
     *
8293
     * @param string $action     (add/edit)
8294
     * @param int    $id         ID of the lp_item (if already exists)
8295
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8296
     *
8297
     * @throws Exception
8298
     * @throws HTML_QuickForm_Error
8299
     *
8300
     * @return string HTML form
8301
     */
8302
    public function display_document_form($action = 'add', $id = 0, $extra_info = 'new')
8303
    {
8304
        $course_id = api_get_course_int_id();
8305
        $_course = api_get_course_info();
8306
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8307
8308
        $no_display_edit_textarea = false;
8309
        $item_description = '';
8310
        //If action==edit document
8311
        //We don't display the document form if it's not an editable document (html or txt file)
8312
        if ($action === 'edit') {
8313
            if (is_array($extra_info)) {
8314
                $path_parts = pathinfo($extra_info['dir']);
8315
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8316
                    $no_display_edit_textarea = true;
8317
                }
8318
            }
8319
        }
8320
        $no_display_add = false;
8321
8322
        // If action==add an existing document
8323
        // We don't display the document form if it's not an editable document (html or txt file).
8324
        if ($action === 'add') {
8325
            if (is_numeric($extra_info)) {
8326
                $extra_info = (int) $extra_info;
8327
                $sql_doc = "SELECT path FROM $tbl_doc
8328
                            WHERE c_id = $course_id AND iid = ".$extra_info;
8329
                $result = Database::query($sql_doc);
8330
                $path_file = Database::result($result, 0, 0);
8331
                $path_parts = pathinfo($path_file);
8332
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8333
                    $no_display_add = true;
8334
                }
8335
            }
8336
        }
8337
8338
        $item_title = '';
8339
        $item_description = '';
8340
        if ($id != 0 && is_array($extra_info)) {
8341
            $item_title = stripslashes($extra_info['title']);
8342
            $item_description = stripslashes($extra_info['description']);
8343
            if (empty($item_title)) {
8344
                $path_parts = pathinfo($extra_info['path']);
8345
                $item_title = stripslashes($path_parts['filename']);
8346
            }
8347
        } elseif (is_numeric($extra_info)) {
8348
            $sql = "SELECT path, title FROM $tbl_doc
8349
                    WHERE
8350
                        c_id = ".$course_id." AND
8351
                        iid = ".intval($extra_info);
8352
            $result = Database::query($sql);
8353
            $row = Database::fetch_array($result);
8354
            $item_title = $row['title'];
8355
            $item_title = str_replace('_', ' ', $item_title);
8356
            if (empty($item_title)) {
8357
                $path_parts = pathinfo($row['path']);
8358
                $item_title = stripslashes($path_parts['filename']);
8359
            }
8360
        }
8361
8362
        $return = '<legend>';
8363
        $parent = 0;
8364
        if ($id != 0 && is_array($extra_info)) {
8365
            $parent = $extra_info['parent_item_id'];
8366
        }
8367
8368
        $arrLP = $this->getItemsForForm();
8369
        $this->tree_array($arrLP);
8370
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8371
        unset($this->arrMenu);
8372
8373
        if ($action == 'add') {
8374
            $return .= get_lang('Create a new document');
8375
        } elseif ($action == 'move') {
8376
            $return .= get_lang('Move the current document');
8377
        } else {
8378
            $return .= get_lang('Edit the current document');
8379
        }
8380
        $return .= '</legend>';
8381
8382
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
8383
            $return .= Display::return_message(
8384
                '<strong>'.get_lang('Warning ! ! !').' !</strong><br />'.get_lang('Warning ! ! !EditingDocument'),
8385
                false
8386
            );
8387
        }
8388
        $form = new FormValidator(
8389
            'form',
8390
            'POST',
8391
            $this->getCurrentBuildingModeURL(),
8392
            '',
8393
            ['enctype' => 'multipart/form-data']
8394
        );
8395
        $defaults['title'] = Security::remove_XSS($item_title);
8396
        if (empty($item_title)) {
8397
            $defaults['title'] = Security::remove_XSS($item_title);
8398
        }
8399
        $defaults['description'] = $item_description;
8400
        $form->addElement('html', $return);
8401
8402
        if ($action != 'move') {
8403
            $data = $this->generate_lp_folder($_course);
8404
            if ($action != 'edit') {
8405
                $folders = DocumentManager::get_all_document_folders(
8406
                    $_course,
8407
                    0,
8408
                    true
8409
                );
8410
                DocumentManager::build_directory_selector(
8411
                    $folders,
8412
                    '',
8413
                    [],
8414
                    true,
8415
                    $form,
8416
                    'directory_parent_id'
8417
                );
8418
            }
8419
8420
            if (isset($data['id'])) {
8421
                $defaults['directory_parent_id'] = $data['id'];
8422
            }
8423
            $this->setItemTitle($form);
8424
        }
8425
8426
        $arrHide[0]['value'] = $this->name;
8427
        $arrHide[0]['padding'] = 20;
8428
8429
        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...
8430
            if ($action != 'add') {
8431
                if ($arrLP[$i]['item_type'] == 'dir' &&
8432
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8433
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8434
                ) {
8435
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8436
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8437
                }
8438
            } else {
8439
                if ($arrLP[$i]['item_type'] == 'dir') {
8440
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8441
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8442
                }
8443
            }
8444
        }
8445
8446
        $parentSelect = $form->addSelect(
8447
            'parent',
8448
            get_lang('Parent'),
8449
            [],
8450
            [
8451
                'id' => 'idParent',
8452
                'onchange' => 'javascript: load_cbo(this.value);',
8453
            ]
8454
        );
8455
8456
        $my_count = 0;
8457
        foreach ($arrHide as $key => $value) {
8458
            if ($my_count != 0) {
8459
                // The LP name is also the first section and is not in the same charset like the other sections.
8460
                $value['value'] = Security::remove_XSS($value['value']);
8461
                $parentSelect->addOption(
8462
                    $value['value'],
8463
                    $key,
8464
                    'style="padding-left:'.$value['padding'].'px;"'
8465
                );
8466
            } else {
8467
                $value['value'] = Security::remove_XSS($value['value']);
8468
                $parentSelect->addOption(
8469
                    $value['value'],
8470
                    $key,
8471
                    'style="padding-left:'.$value['padding'].'px;"'
8472
                );
8473
            }
8474
            $my_count++;
8475
        }
8476
8477
        if (!empty($id)) {
8478
            $parentSelect->setSelected($parent);
8479
        } else {
8480
            $parent_item_id = Session::read('parent_item_id', 0);
8481
            $parentSelect->setSelected($parent_item_id);
8482
        }
8483
8484
        if (is_array($arrLP)) {
8485
            reset($arrLP);
8486
        }
8487
8488
        $arrHide = [];
8489
        // POSITION
8490
        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...
8491
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
8492
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
8493
            ) {
8494
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8495
            }
8496
        }
8497
8498
        $selectedPosition = isset($extra_info['previous_item_id']) ? $extra_info['previous_item_id'] : 0;
8499
8500
        $position = $form->addSelect(
8501
            'previous',
8502
            get_lang('Position'),
8503
            [],
8504
            ['id' => 'previous']
8505
        );
8506
8507
        $position->addOption(get_lang('First position'), 0);
8508
        foreach ($arrHide as $key => $value) {
8509
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8510
            $position->addOption(
8511
                $value['value'],
8512
                $key,
8513
                'style="padding-left:'.$padding.'px;"'
8514
            );
8515
        }
8516
8517
        $position->setSelected($selectedPosition);
8518
8519
        if (is_array($arrLP)) {
8520
            reset($arrLP);
8521
        }
8522
8523
        if ($action != 'move') {
8524
            $arrHide = [];
8525
            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...
8526
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
8527
                    $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8528
                ) {
8529
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8530
                }
8531
            }
8532
8533
            if (!$no_display_add) {
8534
                $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8535
                $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8536
                if ($extra_info == 'new' || $item_type == TOOL_DOCUMENT ||
8537
                    $item_type == TOOL_LP_FINAL_ITEM || $edit == 'true'
8538
                ) {
8539
                    if (isset($_POST['content'])) {
8540
                        $content = stripslashes($_POST['content']);
8541
                    } elseif (is_array($extra_info)) {
8542
                        //If it's an html document or a text file
8543
                        if (!$no_display_edit_textarea) {
8544
                            $content = $this->display_document(
8545
                                $extra_info['path'],
8546
                                false,
8547
                                false
8548
                            );
8549
                        }
8550
                    } elseif (is_numeric($extra_info)) {
8551
                        $content = $this->display_document(
8552
                            $extra_info,
8553
                            false,
8554
                            false
8555
                        );
8556
                    } else {
8557
                        $content = '';
8558
                    }
8559
8560
                    if (!$no_display_edit_textarea) {
8561
                        // We need to calculate here some specific settings for the online editor.
8562
                        // The calculated settings work for documents in the Documents tool
8563
                        // (on the root or in subfolders).
8564
                        // For documents in native scorm packages it is unclear whether the
8565
                        // online editor should be activated or not.
8566
8567
                        // A new document, it is in the root of the repository.
8568
                        $relative_path = '';
8569
                        $relative_prefix = '';
8570
                        if (is_array($extra_info) && $extra_info != 'new') {
8571
                            // The document already exists. Whe have to determine its relative path towards the repository root.
8572
                            $relative_path = explode('/', $extra_info['dir']);
8573
                            $cnt = count($relative_path) - 2;
8574
                            if ($cnt < 0) {
8575
                                $cnt = 0;
8576
                            }
8577
                            $relative_prefix = str_repeat('../', $cnt);
8578
                            $relative_path = array_slice($relative_path, 1, $cnt);
8579
                            $relative_path = implode('/', $relative_path);
8580
                            if (strlen($relative_path) > 0) {
8581
                                $relative_path = $relative_path.'/';
8582
                            }
8583
                        } else {
8584
                            $result = $this->generate_lp_folder($_course);
8585
                            $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
8586
                            $relative_prefix = '../../';
8587
                        }
8588
8589
                        $editor_config = [
8590
                            'ToolbarSet' => 'LearningPathDocuments',
8591
                            'Width' => '100%',
8592
                            'Height' => '500',
8593
                            'FullPage' => true,
8594
                            'CreateDocumentDir' => $relative_prefix,
8595
                            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
8596
                            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
8597
                        ];
8598
8599
                        if ($_GET['action'] == 'add_item') {
8600
                            $class = 'add';
8601
                            $text = get_lang('Add this document to the course');
8602
                        } else {
8603
                            if ($_GET['action'] == 'edit_item') {
8604
                                $class = 'save';
8605
                                $text = get_lang('Save document');
8606
                            }
8607
                        }
8608
8609
                        $form->addButtonSave($text, 'submit_button');
8610
                        $renderer = $form->defaultRenderer();
8611
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
8612
                        $form->addElement('html', '<div class="editor-lp">');
8613
                        $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
8614
                        $form->addElement('html', '</div>');
8615
                        $defaults['content_lp'] = $content;
8616
                    }
8617
                } elseif (is_numeric($extra_info)) {
8618
                    $form->addButtonSave(get_lang('Save document'), 'submit_button');
8619
8620
                    $return = $this->display_document($extra_info, true, true, true);
8621
                    $form->addElement('html', $return);
8622
                }
8623
            }
8624
        }
8625
        if (isset($extra_info['item_type']) &&
8626
            $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
8627
        ) {
8628
            $parentSelect->freeze();
8629
            $position->freeze();
8630
        }
8631
8632
        if ($action == 'move') {
8633
            $form->addElement('hidden', 'title', $item_title);
8634
            $form->addElement('hidden', 'description', $item_description);
8635
        }
8636
        if (is_numeric($extra_info)) {
8637
            $form->addButtonSave(get_lang('Save document'), 'submit_button');
8638
            $form->addElement('hidden', 'path', $extra_info);
8639
        } elseif (is_array($extra_info)) {
8640
            $form->addButtonSave(get_lang('Save document'), 'submit_button');
8641
            $form->addElement('hidden', 'path', $extra_info['path']);
8642
        }
8643
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
8644
        $form->addElement('hidden', 'post_time', time());
8645
        $form->setDefaults($defaults);
8646
8647
        return $form->returnForm();
8648
    }
8649
8650
    /**
8651
     * Returns the form to update or create a read-out text.
8652
     *
8653
     * @param string $action     "add" or "edit"
8654
     * @param int    $id         ID of the lp_item (if already exists)
8655
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8656
     *
8657
     * @throws Exception
8658
     * @throws HTML_QuickForm_Error
8659
     *
8660
     * @return string HTML form
8661
     */
8662
    public function displayFrmReadOutText($action = 'add', $id = 0, $extra_info = 'new')
8663
    {
8664
        $course_id = api_get_course_int_id();
8665
        $_course = api_get_course_info();
8666
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8667
8668
        $no_display_edit_textarea = false;
8669
        $item_description = '';
8670
        //If action==edit document
8671
        //We don't display the document form if it's not an editable document (html or txt file)
8672
        if ($action == 'edit') {
8673
            if (is_array($extra_info)) {
8674
                $path_parts = pathinfo($extra_info['dir']);
8675
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
8676
                    $no_display_edit_textarea = true;
8677
                }
8678
            }
8679
        }
8680
        $no_display_add = false;
8681
8682
        $item_title = '';
8683
        $item_description = '';
8684
        if ($id != 0 && is_array($extra_info)) {
8685
            $item_title = stripslashes($extra_info['title']);
8686
            $item_description = stripslashes($extra_info['description']);
8687
            $item_terms = stripslashes($extra_info['terms']);
8688
            if (empty($item_title)) {
8689
                $path_parts = pathinfo($extra_info['path']);
8690
                $item_title = stripslashes($path_parts['filename']);
8691
            }
8692
        } elseif (is_numeric($extra_info)) {
8693
            $sql = "SELECT path, title FROM $tbl_doc WHERE c_id = ".$course_id." AND iid = ".intval($extra_info);
8694
            $result = Database::query($sql);
8695
            $row = Database::fetch_array($result);
8696
            $item_title = $row['title'];
8697
            $item_title = str_replace('_', ' ', $item_title);
8698
            if (empty($item_title)) {
8699
                $path_parts = pathinfo($row['path']);
8700
                $item_title = stripslashes($path_parts['filename']);
8701
            }
8702
        }
8703
8704
        $parent = 0;
8705
        if ($id != 0 && is_array($extra_info)) {
8706
            $parent = $extra_info['parent_item_id'];
8707
        }
8708
8709
        $arrLP = $this->getItemsForForm();
8710
        $this->tree_array($arrLP);
8711
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8712
        unset($this->arrMenu);
8713
8714
        if ($action === 'add') {
8715
            $formHeader = get_lang('Create a new document');
8716
        } else {
8717
            $formHeader = get_lang('Edit the current document');
8718
        }
8719
8720
        if ('edit' === $action) {
8721
            $urlAudioIcon = Display::url(
8722
                Display::return_icon('audio.png', get_lang('Create read-out text'), [], ICON_SIZE_TINY),
8723
                api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'&'
8724
                    .http_build_query(['view' => 'build', 'id' => $id, 'action' => 'add_audio'])
8725
            );
8726
        } else {
8727
            $urlAudioIcon = Display::return_icon('audio.png', get_lang('Create read-out text'), [], ICON_SIZE_TINY);
8728
        }
8729
8730
        $form = new FormValidator(
8731
            'frm_add_reading',
8732
            'POST',
8733
            $this->getCurrentBuildingModeURL(),
8734
            '',
8735
            ['enctype' => 'multipart/form-data']
8736
        );
8737
        $form->addHeader($formHeader);
8738
        $form->addHtml(
8739
            Display::return_message(
8740
                sprintf(get_lang('You need attach a audio file according to the text, clicking on the %s icon.'), $urlAudioIcon),
8741
                'normal',
8742
                false
8743
            )
8744
        );
8745
        $defaults['title'] = !empty($item_title) ? Security::remove_XSS($item_title) : '';
8746
        $defaults['description'] = $item_description;
8747
8748
        $data = $this->generate_lp_folder($_course);
8749
8750
        if ($action != 'edit') {
8751
            $folders = DocumentManager::get_all_document_folders($_course, 0, true);
8752
            DocumentManager::build_directory_selector(
8753
                $folders,
8754
                '',
8755
                [],
8756
                true,
8757
                $form,
8758
                'directory_parent_id'
8759
            );
8760
        }
8761
8762
        if (isset($data['id'])) {
8763
            $defaults['directory_parent_id'] = $data['id'];
8764
        }
8765
        $this->setItemTitle($form);
8766
8767
        $arrHide[0]['value'] = $this->name;
8768
        $arrHide[0]['padding'] = 20;
8769
8770
        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...
8771
            if ($action != 'add') {
8772
                if ($arrLP[$i]['item_type'] == 'dir' &&
8773
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8774
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8775
                ) {
8776
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8777
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8778
                }
8779
            } else {
8780
                if ($arrLP[$i]['item_type'] == 'dir') {
8781
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8782
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8783
                }
8784
            }
8785
        }
8786
8787
        $parent_select = $form->addSelect(
8788
            'parent',
8789
            get_lang('Parent'),
8790
            [],
8791
            ['onchange' => "javascript: load_cbo(this.value, 'frm_add_reading_previous');"]
8792
        );
8793
8794
        $my_count = 0;
8795
        foreach ($arrHide as $key => $value) {
8796
            if ($my_count != 0) {
8797
                // The LP name is also the first section and is not in the same charset like the other sections.
8798
                $value['value'] = Security::remove_XSS($value['value']);
8799
                $parent_select->addOption(
8800
                    $value['value'],
8801
                    $key,
8802
                    'style="padding-left:'.$value['padding'].'px;"'
8803
                );
8804
            } else {
8805
                $value['value'] = Security::remove_XSS($value['value']);
8806
                $parent_select->addOption(
8807
                    $value['value'],
8808
                    $key,
8809
                    'style="padding-left:'.$value['padding'].'px;"'
8810
                );
8811
            }
8812
            $my_count++;
8813
        }
8814
8815
        if (!empty($id)) {
8816
            $parent_select->setSelected($parent);
8817
        } else {
8818
            $parent_item_id = Session::read('parent_item_id', 0);
8819
            $parent_select->setSelected($parent_item_id);
8820
        }
8821
8822
        if (is_array($arrLP)) {
8823
            reset($arrLP);
8824
        }
8825
8826
        $arrHide = [];
8827
        $s_selected_position = null;
8828
8829
        // POSITION
8830
        $lastPosition = null;
8831
8832
        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...
8833
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) &&
8834
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8835
            ) {
8836
                if ((isset($extra_info['previous_item_id']) &&
8837
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
8838
                ) {
8839
                    $s_selected_position = $arrLP[$i]['id'];
8840
                }
8841
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8842
            }
8843
            $lastPosition = $arrLP[$i]['id'];
8844
        }
8845
8846
        if (empty($s_selected_position)) {
8847
            $s_selected_position = $lastPosition;
8848
        }
8849
8850
        $position = $form->addSelect(
8851
            'previous',
8852
            get_lang('Position'),
8853
            []
8854
        );
8855
        $position->addOption(get_lang('First position'), 0);
8856
8857
        foreach ($arrHide as $key => $value) {
8858
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8859
            $position->addOption(
8860
                $value['value'],
8861
                $key,
8862
                'style="padding-left:'.$padding.'px;"'
8863
            );
8864
        }
8865
        $position->setSelected($s_selected_position);
8866
8867
        if (is_array($arrLP)) {
8868
            reset($arrLP);
8869
        }
8870
8871
        $arrHide = [];
8872
8873
        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...
8874
            if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
8875
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8876
            ) {
8877
                $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8878
            }
8879
        }
8880
8881
        if (!$no_display_add) {
8882
            $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8883
            $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8884
8885
            if ($extra_info == 'new' || $item_type == TOOL_READOUT_TEXT || $edit == 'true') {
8886
                if (!$no_display_edit_textarea) {
8887
                    $content = '';
8888
8889
                    if (isset($_POST['content'])) {
8890
                        $content = stripslashes($_POST['content']);
8891
                    } elseif (is_array($extra_info)) {
8892
                        $content = $this->display_document($extra_info['path'], false, false);
8893
                    } elseif (is_numeric($extra_info)) {
8894
                        $content = $this->display_document($extra_info, false, false);
8895
                    }
8896
8897
                    // A new document, it is in the root of the repository.
8898
                    if (is_array($extra_info) && $extra_info != 'new') {
8899
                    } else {
8900
                        $this->generate_lp_folder($_course);
8901
                    }
8902
8903
                    if ($_GET['action'] == 'add_item') {
8904
                        $text = get_lang('Add this document to the course');
8905
                    } else {
8906
                        $text = get_lang('Save document');
8907
                    }
8908
8909
                    $form->addTextarea('content_lp', get_lang('Content'), ['rows' => 20]);
8910
                    $form
8911
                        ->defaultRenderer()
8912
                        ->setElementTemplate($form->getDefaultElementTemplate(), 'content_lp');
8913
                    $form->addButtonSave($text, 'submit_button');
8914
                    $defaults['content_lp'] = $content;
8915
                }
8916
            } elseif (is_numeric($extra_info)) {
8917
                $form->addButtonSave(get_lang('Save document'), 'submit_button');
8918
8919
                $return = $this->display_document($extra_info, true, true, true);
8920
                $form->addElement('html', $return);
8921
            }
8922
        }
8923
8924
        if (is_numeric($extra_info)) {
8925
            $form->addElement('hidden', 'path', $extra_info);
8926
        } elseif (is_array($extra_info)) {
8927
            $form->addElement('hidden', 'path', $extra_info['path']);
8928
        }
8929
8930
        $form->addElement('hidden', 'type', TOOL_READOUT_TEXT);
8931
        $form->addElement('hidden', 'post_time', time());
8932
        $form->setDefaults($defaults);
8933
8934
        return $form->returnForm();
8935
    }
8936
8937
    /**
8938
     * @param array  $courseInfo
8939
     * @param string $content
8940
     * @param string $title
8941
     * @param int    $parentId
8942
     *
8943
     * @throws \Doctrine\ORM\ORMException
8944
     * @throws \Doctrine\ORM\OptimisticLockException
8945
     * @throws \Doctrine\ORM\TransactionRequiredException
8946
     *
8947
     * @return int
8948
     */
8949
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
8950
    {
8951
        $creatorId = api_get_user_id();
8952
        $sessionId = api_get_session_id();
8953
8954
        // Generates folder
8955
        $result = $this->generate_lp_folder($courseInfo);
8956
        $dir = $result['dir'];
8957
8958
        if (empty($parentId) || $parentId == '/') {
8959
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
8960
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
8961
8962
            if ($parentId === '/') {
8963
                $dir = '/';
8964
            }
8965
8966
            // Please, do not modify this dirname formatting.
8967
            if (strstr($dir, '..')) {
8968
                $dir = '/';
8969
            }
8970
8971
            if (!empty($dir[0]) && $dir[0] == '.') {
8972
                $dir = substr($dir, 1);
8973
            }
8974
            if (!empty($dir[0]) && $dir[0] != '/') {
8975
                $dir = '/'.$dir;
8976
            }
8977
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
8978
                $dir .= '/';
8979
            }
8980
        } else {
8981
            $parentInfo = DocumentManager::get_document_data_by_id(
8982
                $parentId,
8983
                $courseInfo['code']
8984
            );
8985
            if (!empty($parentInfo)) {
8986
                $dir = $parentInfo['path'].'/';
8987
            }
8988
        }
8989
8990
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
8991
8992
        if (!is_dir($filepath)) {
8993
            $dir = '/';
8994
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
8995
        }
8996
8997
        $originalTitle = !empty($title) ? $title : $_POST['title'];
8998
8999
        if (!empty($title)) {
9000
            $title = api_replace_dangerous_char(stripslashes($title));
9001
        } else {
9002
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
9003
        }
9004
9005
        $title = disable_dangerous_file($title);
9006
        $filename = $title;
9007
        $content = !empty($content) ? $content : $_POST['content_lp'];
9008
        $tmpFileName = $filename;
9009
9010
        $i = 0;
9011
        while (file_exists($filepath.$tmpFileName.'.html')) {
9012
            $tmpFileName = $filename.'_'.++$i;
9013
        }
9014
9015
        $filename = $tmpFileName.'.html';
9016
        $content = stripslashes($content);
9017
9018
        if (file_exists($filepath.$filename)) {
9019
            return 0;
9020
        }
9021
9022
        $putContent = file_put_contents($filepath.$filename, $content);
9023
9024
        if ($putContent === false) {
9025
            return 0;
9026
        }
9027
9028
        $fileSize = filesize($filepath.$filename);
9029
        $saveFilePath = $dir.$filename;
9030
9031
        $document = DocumentManager::addDocument(
9032
            $courseInfo,
9033
            $saveFilePath,
9034
            'file',
9035
            $fileSize,
9036
            $tmpFileName,
9037
            '',
9038
            0, //readonly
9039
            true,
9040
            null,
9041
            $sessionId,
9042
            $creatorId
9043
        );
9044
9045
        $documentId = $document->getId();
9046
9047
        if (!$document) {
9048
            return 0;
9049
        }
9050
9051
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
9052
        $newTitle = $originalTitle;
9053
9054
        if ($newComment || $newTitle) {
9055
            $em = Database::getManager();
9056
9057
            if ($newComment) {
9058
                $document->setComment($newComment);
9059
            }
9060
9061
            if ($newTitle) {
9062
                $document->setTitle($newTitle);
9063
            }
9064
9065
            $em->persist($document);
9066
            $em->flush();
9067
        }
9068
9069
        return $documentId;
9070
    }
9071
9072
    /**
9073
     * Return HTML form to add/edit a link item.
9074
     *
9075
     * @param string $action     (add/edit)
9076
     * @param int    $id         Item ID if exists
9077
     * @param mixed  $extra_info
9078
     *
9079
     * @throws Exception
9080
     * @throws HTML_QuickForm_Error
9081
     *
9082
     * @return string HTML form
9083
     */
9084
    public function display_link_form($action = 'add', $id = 0, $extra_info = '')
9085
    {
9086
        $course_id = api_get_course_int_id();
9087
        $tbl_link = Database::get_course_table(TABLE_LINK);
9088
9089
        $item_title = '';
9090
        $item_description = '';
9091
        $item_url = '';
9092
9093
        if ($id != 0 && is_array($extra_info)) {
9094
            $item_title = stripslashes($extra_info['title']);
9095
            $item_description = stripslashes($extra_info['description']);
9096
            $item_url = stripslashes($extra_info['url']);
9097
        } elseif (is_numeric($extra_info)) {
9098
            $extra_info = (int) $extra_info;
9099
            $sql = "SELECT title, description, url
9100
                    FROM $tbl_link
9101
                    WHERE c_id = $course_id AND iid = $extra_info";
9102
            $result = Database::query($sql);
9103
            $row = Database::fetch_array($result);
9104
            $item_title = $row['title'];
9105
            $item_description = $row['description'];
9106
            $item_url = $row['url'];
9107
        }
9108
9109
        $form = new FormValidator(
9110
            'edit_link',
9111
            'POST',
9112
            $this->getCurrentBuildingModeURL()
9113
        );
9114
        $defaults = [];
9115
        $parent = 0;
9116
        if ($id != 0 && is_array($extra_info)) {
9117
            $parent = $extra_info['parent_item_id'];
9118
        }
9119
9120
        $arrLP = $this->getItemsForForm();
9121
9122
        $this->tree_array($arrLP);
9123
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9124
        unset($this->arrMenu);
9125
9126
        if ($action == 'add') {
9127
            $legend = get_lang('Adding a link to the course');
9128
        } elseif ($action == 'move') {
9129
            $legend = get_lang('Move the current link');
9130
        } else {
9131
            $legend = get_lang('Edit the current link');
9132
        }
9133
9134
        $form->addHeader($legend);
9135
9136
        if ($action != 'move') {
9137
            $this->setItemTitle($form);
9138
            $defaults['title'] = $item_title;
9139
        }
9140
9141
        $selectParent = $form->addSelect(
9142
            'parent',
9143
            get_lang('Parent'),
9144
            [],
9145
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
9146
        );
9147
        $selectParent->addOption($this->name, 0);
9148
        $arrHide = [
9149
            $id,
9150
        ];
9151
9152
        $parent_item_id = Session::read('parent_item_id', 0);
9153
9154
        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...
9155
            if ($action != 'add') {
9156
                if (
9157
                    ($arrLP[$i]['item_type'] == 'dir') &&
9158
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9159
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9160
                ) {
9161
                    $selectParent->addOption(
9162
                        $arrLP[$i]['title'],
9163
                        $arrLP[$i]['id'],
9164
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
9165
                    );
9166
9167
                    if ($parent == $arrLP[$i]['id']) {
9168
                        $selectParent->setSelected($arrLP[$i]['id']);
9169
                    }
9170
                } else {
9171
                    $arrHide[] = $arrLP[$i]['id'];
9172
                }
9173
            } else {
9174
                if ($arrLP[$i]['item_type'] == 'dir') {
9175
                    $selectParent->addOption(
9176
                        $arrLP[$i]['title'],
9177
                        $arrLP[$i]['id'],
9178
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
9179
                    );
9180
9181
                    if ($parent_item_id == $arrLP[$i]['id']) {
9182
                        $selectParent->setSelected($arrLP[$i]['id']);
9183
                    }
9184
                }
9185
            }
9186
        }
9187
9188
        if (is_array($arrLP)) {
9189
            reset($arrLP);
9190
        }
9191
9192
        $selectPrevious = $form->addSelect(
9193
            'previous',
9194
            get_lang('Position'),
9195
            [],
9196
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9197
        );
9198
        $selectPrevious->addOption(get_lang('First position'), 0);
9199
9200
        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...
9201
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9202
                $selectPrevious->addOption(
9203
                    $arrLP[$i]['title'],
9204
                    $arrLP[$i]['id']
9205
                );
9206
9207
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9208
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9209
                } elseif ($action == 'add') {
9210
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9211
                }
9212
            }
9213
        }
9214
9215
        if ($action != 'move') {
9216
            $urlAttributes = ['class' => 'learnpath_item_form'];
9217
9218
            if (is_numeric($extra_info)) {
9219
                $urlAttributes['disabled'] = 'disabled';
9220
            }
9221
9222
            $form->addElement('url', 'url', get_lang('URL'), $urlAttributes);
9223
            $defaults['url'] = $item_url;
9224
            $arrHide = [];
9225
            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...
9226
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
9227
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9228
                }
9229
            }
9230
        }
9231
9232
        if ($action == 'add') {
9233
            $form->addButtonSave(get_lang('Add link to course'), 'submit_button');
9234
        } else {
9235
            $form->addButtonSave(get_lang('Edit the current link'), 'submit_button');
9236
        }
9237
9238
        if ($action == 'move') {
9239
            $form->addHidden('title', $item_title);
9240
            $form->addHidden('description', $item_description);
9241
        }
9242
9243
        if (is_numeric($extra_info)) {
9244
            $form->addHidden('path', $extra_info);
9245
        } elseif (is_array($extra_info)) {
9246
            $form->addHidden('path', $extra_info['path']);
9247
        }
9248
        $form->addHidden('type', TOOL_LINK);
9249
        $form->addHidden('post_time', time());
9250
        $form->setDefaults($defaults);
9251
9252
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
9253
    }
9254
9255
    /**
9256
     * Return HTML form to add/edit a student publication (work).
9257
     *
9258
     * @param string $action
9259
     * @param int    $id         Item ID if already exists
9260
     * @param string $extra_info
9261
     *
9262
     * @throws Exception
9263
     *
9264
     * @return string HTML form
9265
     */
9266
    public function display_student_publication_form(
9267
        $action = 'add',
9268
        $id = 0,
9269
        $extra_info = ''
9270
    ) {
9271
        $course_id = api_get_course_int_id();
9272
        $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
9273
9274
        $item_title = get_lang('Assignments');
9275
        if ($id != 0 && is_array($extra_info)) {
9276
            $item_title = stripslashes($extra_info['title']);
9277
            $item_description = stripslashes($extra_info['description']);
9278
        } elseif (is_numeric($extra_info)) {
9279
            $extra_info = (int) $extra_info;
9280
            $sql = "SELECT title, description
9281
                    FROM $tbl_publication
9282
                    WHERE c_id = $course_id AND id = ".$extra_info;
9283
9284
            $result = Database::query($sql);
9285
            $row = Database::fetch_array($result);
9286
            if ($row) {
9287
                $item_title = $row['title'];
9288
            }
9289
        }
9290
9291
        $parent = 0;
9292
        if ($id != 0 && is_array($extra_info)) {
9293
            $parent = $extra_info['parent_item_id'];
9294
        }
9295
9296
        $arrLP = $this->getItemsForForm();
9297
9298
        $this->tree_array($arrLP);
9299
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9300
        unset($this->arrMenu);
9301
9302
        $form = new FormValidator('frm_student_publication', 'post', '#');
9303
9304
        if ($action == 'add') {
9305
            $form->addHeader(get_lang('Assignments'));
9306
        } elseif ($action == 'move') {
9307
            $form->addHeader(get_lang('Move the current assignment'));
9308
        } else {
9309
            $form->addHeader(get_lang('Edit the current assignment'));
9310
        }
9311
9312
        if ($action != 'move') {
9313
            $this->setItemTitle($form);
9314
        }
9315
9316
        $parentSelect = $form->addSelect(
9317
            'parent',
9318
            get_lang('Parent'),
9319
            ['0' => $this->name],
9320
            [
9321
                'onchange' => 'javascript: load_cbo(this.value);',
9322
                'class' => 'learnpath_item_form',
9323
                'id' => 'idParent',
9324
            ]
9325
        );
9326
9327
        $arrHide = [$id];
9328
        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...
9329
            if ($action != 'add') {
9330
                if (
9331
                    ($arrLP[$i]['item_type'] == 'dir') &&
9332
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9333
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9334
                ) {
9335
                    $parentSelect->addOption(
9336
                        $arrLP[$i]['title'],
9337
                        $arrLP[$i]['id'],
9338
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9339
                    );
9340
9341
                    if ($parent == $arrLP[$i]['id']) {
9342
                        $parentSelect->setSelected($arrLP[$i]['id']);
9343
                    }
9344
                } else {
9345
                    $arrHide[] = $arrLP[$i]['id'];
9346
                }
9347
            } else {
9348
                if ($arrLP[$i]['item_type'] == 'dir') {
9349
                    $parentSelect->addOption(
9350
                        $arrLP[$i]['title'],
9351
                        $arrLP[$i]['id'],
9352
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9353
                    );
9354
9355
                    if ($parent == $arrLP[$i]['id']) {
9356
                        $parentSelect->setSelected($arrLP[$i]['id']);
9357
                    }
9358
                }
9359
            }
9360
        }
9361
9362
        if (is_array($arrLP)) {
9363
            reset($arrLP);
9364
        }
9365
9366
        $previousSelect = $form->addSelect(
9367
            'previous',
9368
            get_lang('Position'),
9369
            ['0' => get_lang('First position')],
9370
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9371
        );
9372
9373
        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...
9374
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9375
                $previousSelect->addOption(
9376
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
9377
                    $arrLP[$i]['id']
9378
                );
9379
9380
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9381
                    $previousSelect->setSelected($arrLP[$i]['id']);
9382
                } elseif ($action == 'add') {
9383
                    $previousSelect->setSelected($arrLP[$i]['id']);
9384
                }
9385
            }
9386
        }
9387
9388
        if ($action == 'add') {
9389
            $form->addButtonCreate(get_lang('Add assignment to course'), 'submit_button');
9390
        } else {
9391
            $form->addButtonCreate(get_lang('Edit the current assignment'), 'submit_button');
9392
        }
9393
9394
        if ($action == 'move') {
9395
            $form->addHidden('title', $item_title);
9396
            $form->addHidden('description', $item_description);
9397
        }
9398
9399
        if (is_numeric($extra_info)) {
9400
            $form->addHidden('path', $extra_info);
9401
        } elseif (is_array($extra_info)) {
9402
            $form->addHidden('path', $extra_info['path']);
9403
        }
9404
9405
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
9406
        $form->addHidden('post_time', time());
9407
        $form->setDefaults(['title' => $item_title]);
9408
9409
        $return = '<div class="sectioncomment">';
9410
        $return .= $form->returnForm();
9411
        $return .= '</div>';
9412
9413
        return $return;
9414
    }
9415
9416
    /**
9417
     * Displays the menu for manipulating a step.
9418
     *
9419
     * @param id     $item_id
9420
     * @param string $item_type
9421
     *
9422
     * @return string
9423
     */
9424
    public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
9425
    {
9426
        $_course = api_get_course_info();
9427
        $course_code = api_get_course_id();
9428
        $item_id = (int) $item_id;
9429
9430
        $return = '<div class="actions">';
9431
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9432
        $sql = "SELECT * FROM $tbl_lp_item
9433
                WHERE iid = ".$item_id;
9434
        $result = Database::query($sql);
9435
        $row = Database::fetch_assoc($result);
9436
9437
        $audio_player = null;
9438
        // We display an audio player if needed.
9439
        if (!empty($row['audio'])) {
9440
            $webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
9441
9442
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
9443
                .'<audio src="'.$webAudioPath.'" controls>'
9444
                .'</div><br>';
9445
        }
9446
9447
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
9448
9449
        if ($item_type != TOOL_LP_FINAL_ITEM) {
9450
            $return .= Display::url(
9451
                Display::return_icon(
9452
                    'edit.png',
9453
                    get_lang('Edit'),
9454
                    [],
9455
                    ICON_SIZE_SMALL
9456
                ),
9457
                $url.'&action=edit_item&path_item='.$row['path']
9458
            );
9459
9460
            $return .= Display::url(
9461
                Display::return_icon(
9462
                    'move.png',
9463
                    get_lang('Move'),
9464
                    [],
9465
                    ICON_SIZE_SMALL
9466
                ),
9467
                $url.'&action=move_item'
9468
            );
9469
        }
9470
9471
        // Commented for now as prerequisites cannot be added to chapters.
9472
        if ($item_type != 'dir') {
9473
            $return .= Display::url(
9474
                Display::return_icon(
9475
                    'accept.png',
9476
                    get_lang('Prerequisites'),
9477
                    [],
9478
                    ICON_SIZE_SMALL
9479
                ),
9480
                $url.'&action=edit_item_prereq'
9481
            );
9482
        }
9483
        $return .= Display::url(
9484
            Display::return_icon(
9485
                'delete.png',
9486
                get_lang('Delete'),
9487
                [],
9488
                ICON_SIZE_SMALL
9489
            ),
9490
            $url.'&action=delete_item'
9491
        );
9492
9493
        if (in_array($item_type, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
9494
            $documentData = DocumentManager::get_document_data_by_id($row['path'], $course_code);
9495
            if (empty($documentData)) {
9496
                // Try with iid
9497
                $table = Database::get_course_table(TABLE_DOCUMENT);
9498
                $sql = "SELECT path FROM $table
9499
                        WHERE
9500
                              c_id = ".api_get_course_int_id()." AND
9501
                              iid = ".$row['path']." AND
9502
                              path NOT LIKE '%_DELETED_%'";
9503
                $result = Database::query($sql);
9504
                $documentData = Database::fetch_array($result);
9505
                if ($documentData) {
9506
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
9507
                }
9508
            }
9509
            if (isset($documentData['absolute_path_from_document'])) {
9510
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
9511
            }
9512
        }
9513
9514
        $return .= '</div>';
9515
9516
        if (!empty($audio_player)) {
9517
            $return .= $audio_player;
9518
        }
9519
9520
        return $return;
9521
    }
9522
9523
    /**
9524
     * Creates the javascript needed for filling up the checkboxes without page reload.
9525
     *
9526
     * @return string
9527
     */
9528
    public function get_js_dropdown_array()
9529
    {
9530
        $course_id = api_get_course_int_id();
9531
        $return = 'var child_name = new Array();'."\n";
9532
        $return .= 'var child_value = new Array();'."\n\n";
9533
        $return .= 'child_name[0] = new Array();'."\n";
9534
        $return .= 'child_value[0] = new Array();'."\n\n";
9535
9536
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9537
        $sql = "SELECT * FROM ".$tbl_lp_item."
9538
                WHERE
9539
                    c_id = $course_id AND
9540
                    lp_id = ".$this->lp_id." AND
9541
                    parent_item_id = 0
9542
                ORDER BY display_order ASC";
9543
        $res_zero = Database::query($sql);
9544
        $i = 0;
9545
9546
        $list = $this->getItemsForForm(true);
9547
9548
        foreach ($list as $row_zero) {
9549
            if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
9550
                if ($row_zero['item_type'] == TOOL_QUIZ) {
9551
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
9552
                }
9553
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
9554
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
9555
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
9556
            }
9557
        }
9558
9559
        $return .= "\n";
9560
        $sql = "SELECT * FROM $tbl_lp_item
9561
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9562
        $res = Database::query($sql);
9563
        while ($row = Database::fetch_array($res)) {
9564
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
9565
                           WHERE
9566
                                c_id = ".$course_id." AND
9567
                                parent_item_id = ".$row['iid']."
9568
                           ORDER BY display_order ASC";
9569
            $res_parent = Database::query($sql_parent);
9570
            $i = 0;
9571
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
9572
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
9573
9574
            while ($row_parent = Database::fetch_array($res_parent)) {
9575
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
9576
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
9577
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
9578
            }
9579
            $return .= "\n";
9580
        }
9581
9582
        $return .= "
9583
            function load_cbo(id) {
9584
                if (!id) {
9585
                    return false;
9586
                }
9587
9588
                var cbo = document.getElementById('previous');
9589
                for(var i = cbo.length - 1; i > 0; i--) {
9590
                    cbo.options[i] = null;
9591
                }
9592
9593
                var k=0;
9594
                for(var i = 1; i <= child_name[id].length; i++){
9595
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
9596
                    option.style.paddingLeft = '40px';
9597
                    cbo.options[i] = option;
9598
                    k = i;
9599
                }
9600
9601
                cbo.options[k].selected = true;
9602
                $('#previous').selectpicker('refresh');
9603
            }";
9604
9605
        return $return;
9606
    }
9607
9608
    /**
9609
     * Display the form to allow moving an item.
9610
     *
9611
     * @param int $item_id Item ID
9612
     *
9613
     * @throws Exception
9614
     * @throws HTML_QuickForm_Error
9615
     *
9616
     * @return string HTML form
9617
     */
9618
    public function display_move_item($item_id)
9619
    {
9620
        $return = '';
9621
        if (is_numeric($item_id)) {
9622
            $item_id = (int) $item_id;
9623
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9624
9625
            $sql = "SELECT * FROM $tbl_lp_item
9626
                    WHERE iid = $item_id";
9627
            $res = Database::query($sql);
9628
            $row = Database::fetch_array($res);
9629
9630
            switch ($row['item_type']) {
9631
                case 'dir':
9632
                case 'asset':
9633
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9634
                    $return .= $this->display_item_form(
9635
                        $row['item_type'],
9636
                        get_lang('Move the current section'),
9637
                        'move',
9638
                        $item_id,
9639
                        $row
9640
                    );
9641
                    break;
9642
                case TOOL_DOCUMENT:
9643
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9644
                    $return .= $this->display_document_form('move', $item_id, $row);
9645
                    break;
9646
                case TOOL_LINK:
9647
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9648
                    $return .= $this->display_link_form('move', $item_id, $row);
9649
                    break;
9650
                case TOOL_HOTPOTATOES:
9651
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9652
                    $return .= $this->display_link_form('move', $item_id, $row);
9653
                    break;
9654
                case TOOL_QUIZ:
9655
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9656
                    $return .= $this->display_quiz_form('move', $item_id, $row);
9657
                    break;
9658
                case TOOL_STUDENTPUBLICATION:
9659
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9660
                    $return .= $this->display_student_publication_form('move', $item_id, $row);
9661
                    break;
9662
                case TOOL_FORUM:
9663
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9664
                    $return .= $this->display_forum_form('move', $item_id, $row);
9665
                    break;
9666
                case TOOL_THREAD:
9667
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9668
                    $return .= $this->display_forum_form('move', $item_id, $row);
9669
                    break;
9670
            }
9671
        }
9672
9673
        return $return;
9674
    }
9675
9676
    /**
9677
     * Return HTML form to allow prerequisites selection.
9678
     *
9679
     * @todo use FormValidator
9680
     *
9681
     * @param int Item ID
9682
     *
9683
     * @return string HTML form
9684
     */
9685
    public function display_item_prerequisites_form($item_id = 0)
9686
    {
9687
        $course_id = api_get_course_int_id();
9688
        $item_id = (int) $item_id;
9689
9690
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9691
9692
        /* Current prerequisite */
9693
        $sql = "SELECT * FROM $tbl_lp_item
9694
                WHERE iid = $item_id";
9695
        $result = Database::query($sql);
9696
        $row = Database::fetch_array($result);
9697
        $prerequisiteId = $row['prerequisite'];
9698
        $return = '<legend>';
9699
        $return .= get_lang('Add/edit prerequisites');
9700
        $return .= '</legend>';
9701
        $return .= '<form method="POST">';
9702
        $return .= '<div class="table-responsive">';
9703
        $return .= '<table class="table table-hover">';
9704
        $return .= '<thead>';
9705
        $return .= '<tr>';
9706
        $return .= '<th>'.get_lang('Prerequisites').'</th>';
9707
        $return .= '<th width="140">'.get_lang('minimum').'</th>';
9708
        $return .= '<th width="140">'.get_lang('maximum').'</th>';
9709
        $return .= '</tr>';
9710
        $return .= '</thead>';
9711
9712
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
9713
        $return .= '<tbody>';
9714
        $return .= '<tr>';
9715
        $return .= '<td colspan="3">';
9716
        $return .= '<div class="radio learnpath"><label for="idnone">';
9717
        $return .= '<input checked="checked" id="idnone" name="prerequisites" type="radio" />';
9718
        $return .= get_lang('none').'</label>';
9719
        $return .= '</div>';
9720
        $return .= '</tr>';
9721
9722
        $sql = "SELECT * FROM $tbl_lp_item
9723
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9724
        $result = Database::query($sql);
9725
9726
        $selectedMinScore = [];
9727
        $selectedMaxScore = [];
9728
        $masteryScore = [];
9729
        while ($row = Database::fetch_array($result)) {
9730
            if ($row['iid'] == $item_id) {
9731
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
9732
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
9733
            }
9734
            $masteryScore[$row['iid']] = $row['mastery_score'];
9735
        }
9736
9737
        $arrLP = $this->getItemsForForm();
9738
        $this->tree_array($arrLP);
9739
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9740
        unset($this->arrMenu);
9741
9742
        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...
9743
            $item = $arrLP[$i];
9744
9745
            if ($item['id'] == $item_id) {
9746
                break;
9747
            }
9748
9749
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
9750
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
9751
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
9752
9753
            $return .= '<tr>';
9754
            $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
9755
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
9756
            $return .= '<label for="id'.$item['id'].'">';
9757
            $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'].'" />';
9758
9759
            $icon_name = str_replace(' ', '', $item['item_type']);
9760
9761
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
9762
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
9763
            } else {
9764
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
9765
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
9766
                } else {
9767
                    $return .= Display::return_icon('folder_document.png');
9768
                }
9769
            }
9770
9771
            $return .= $item['title'].'</label>';
9772
            $return .= '</div>';
9773
            $return .= '</td>';
9774
9775
            if ($item['item_type'] == TOOL_QUIZ) {
9776
                // lets update max_score Tests information depending of the Tests Advanced properties
9777
                $lpItemObj = new LpItem($course_id, $item['id']);
9778
                $exercise = new Exercise($course_id);
9779
                $exercise->read($lpItemObj->path);
9780
                $lpItemObj->max_score = $exercise->get_max_score();
9781
                $lpItemObj->update();
9782
                $item['max_score'] = $lpItemObj->max_score;
9783
9784
                if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
9785
                    // Backwards compatibility with 1.9.x use mastery_score as min value
9786
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
9787
                }
9788
9789
                $return .= '<td>';
9790
                $return .= '<input
9791
                    class="form-control"
9792
                    size="4" 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
                    class="form-control"
9804
                    size="4"
9805
                    maxlength="3"
9806
                    name="max_'.$item['id'].'"
9807
                    type="number"
9808
                    min="0"
9809
                    step="1"
9810
                    max="'.$item['max_score'].'"
9811
                    value="'.$selectedMaxScoreValue.'"
9812
                />';
9813
                $return .= '</td>';
9814
            }
9815
9816
            if ($item['item_type'] == TOOL_HOTPOTATOES) {
9817
                $return .= '<td>';
9818
                $return .= '<input
9819
                    size="4"
9820
                    maxlength="3"
9821
                    name="min_'.$item['id'].'"
9822
                    type="number"
9823
                    min="0"
9824
                    step="1"
9825
                    max="'.$item['max_score'].'"
9826
                    value="'.$selectedMinScoreValue.'"
9827
                />';
9828
                $return .= '</td>';
9829
                $return .= '<td>';
9830
                $return .= '<input
9831
                    size="4"
9832
                    maxlength="3"
9833
                    name="max_'.$item['id'].'"
9834
                    type="number"
9835
                    min="0"
9836
                    step="1"
9837
                    max="'.$item['max_score'].'"
9838
                    value="'.$selectedMaxScoreValue.'"
9839
                />';
9840
                $return .= '</td>';
9841
            }
9842
            $return .= '</tr>';
9843
        }
9844
        $return .= '<tr>';
9845
        $return .= '</tr>';
9846
        $return .= '</tbody>';
9847
        $return .= '</table>';
9848
        $return .= '</div>';
9849
        $return .= '<div class="form-group">';
9850
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
9851
            get_lang('Save prerequisites settings').'</button>';
9852
        $return .= '</form>';
9853
9854
        return $return;
9855
    }
9856
9857
    /**
9858
     * Return HTML list to allow prerequisites selection for lp.
9859
     *
9860
     * @return string HTML form
9861
     */
9862
    public function display_lp_prerequisites_list()
9863
    {
9864
        $course_id = api_get_course_int_id();
9865
        $lp_id = $this->lp_id;
9866
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
9867
9868
        // get current prerequisite
9869
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
9870
        $result = Database::query($sql);
9871
        $row = Database::fetch_array($result);
9872
        $prerequisiteId = $row['prerequisite'];
9873
        $session_id = api_get_session_id();
9874
        $session_condition = api_get_session_condition($session_id, true, true);
9875
        $sql = "SELECT * FROM $tbl_lp
9876
                WHERE c_id = $course_id $session_condition
9877
                ORDER BY display_order ";
9878
        $rs = Database::query($sql);
9879
        $return = '';
9880
        $return .= '<select name="prerequisites" class="form-control">';
9881
        $return .= '<option value="0">'.get_lang('none').'</option>';
9882
        if (Database::num_rows($rs) > 0) {
9883
            while ($row = Database::fetch_array($rs)) {
9884
                if ($row['id'] == $lp_id) {
9885
                    continue;
9886
                }
9887
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
9888
            }
9889
        }
9890
        $return .= '</select>';
9891
9892
        return $return;
9893
    }
9894
9895
    /**
9896
     * Creates a list with all the documents in it.
9897
     *
9898
     * @param bool $showInvisibleFiles
9899
     *
9900
     * @throws Exception
9901
     * @throws HTML_QuickForm_Error
9902
     *
9903
     * @return string
9904
     */
9905
    public function get_documents($showInvisibleFiles = false)
9906
    {
9907
        $course_info = api_get_course_info();
9908
        $sessionId = api_get_session_id();
9909
        $documentTree = DocumentManager::get_document_preview(
9910
            $course_info,
9911
            $this->lp_id,
9912
            null,
9913
            $sessionId,
9914
            true,
9915
            null,
9916
            null,
9917
            $showInvisibleFiles,
9918
            true
9919
        );
9920
9921
        $headers = [
9922
            get_lang('Files'),
9923
            get_lang('Create a new document'),
9924
            get_lang('Create read-out text'),
9925
            get_lang('Upload'),
9926
        ];
9927
9928
        $form = new FormValidator(
9929
            'form_upload',
9930
            'POST',
9931
            $this->getCurrentBuildingModeURL(),
9932
            '',
9933
            ['enctype' => 'multipart/form-data']
9934
        );
9935
9936
        $folders = DocumentManager::get_all_document_folders(
9937
            api_get_course_info(),
9938
            0,
9939
            true
9940
        );
9941
9942
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
9943
9944
        DocumentManager::build_directory_selector(
9945
            $folders,
9946
            $lpPathInfo['id'],
9947
            [],
9948
            true,
9949
            $form,
9950
            'directory_parent_id'
9951
        );
9952
9953
        $group = [
9954
            $form->createElement(
9955
                'radio',
9956
                'if_exists',
9957
                get_lang('If file exists:'),
9958
                get_lang('Do nothing'),
9959
                'nothing'
9960
            ),
9961
            $form->createElement(
9962
                'radio',
9963
                'if_exists',
9964
                null,
9965
                get_lang('Overwrite the existing file'),
9966
                'overwrite'
9967
            ),
9968
            $form->createElement(
9969
                'radio',
9970
                'if_exists',
9971
                null,
9972
                get_lang('Rename the uploaded file if it exists'),
9973
                'rename'
9974
            ),
9975
        ];
9976
        $form->addGroup($group, null, get_lang('If file exists:'));
9977
9978
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
9979
        $defaultFileExistsOption = 'rename';
9980
        if (!empty($fileExistsOption)) {
9981
            $defaultFileExistsOption = $fileExistsOption;
9982
        }
9983
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
9984
9985
        // Check box options
9986
        $form->addElement(
9987
            'checkbox',
9988
            'unzip',
9989
            get_lang('Options'),
9990
            get_lang('Uncompress zip')
9991
        );
9992
9993
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
9994
        $form->addMultipleUpload($url);
9995
        $new = $this->display_document_form('add', 0);
9996
        $frmReadOutText = $this->displayFrmReadOutText('add');
9997
        $tabs = Display::tabs(
9998
            $headers,
9999
            [$documentTree, $new, $frmReadOutText, $form->returnForm()],
10000
            'subtab'
10001
        );
10002
10003
        return $tabs;
10004
    }
10005
10006
    /**
10007
     * Creates a list with all the exercises (quiz) in it.
10008
     *
10009
     * @return string
10010
     */
10011
    public function get_exercises()
10012
    {
10013
        $course_id = api_get_course_int_id();
10014
        $session_id = api_get_session_id();
10015
        $userInfo = api_get_user_info();
10016
10017
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
10018
        $condition_session = api_get_session_condition($session_id, true, true);
10019
        $setting = api_get_setting('lp.show_invisible_exercise_in_lp_toc') === 'true';
10020
10021
        $activeCondition = ' active <> -1 ';
10022
        if ($setting) {
10023
            $activeCondition = ' active = 1 ';
10024
        }
10025
10026
        $categoryCondition = '';
10027
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
10028
        if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
10029
            $categoryCondition = " AND exercise_category_id = $categoryId ";
10030
        }
10031
10032
        $keywordCondition = '';
10033
        $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
10034
10035
        if (!empty($keyword)) {
10036
            $keyword = Database::escape_string($keyword);
10037
            $keywordCondition = " AND title LIKE '%$keyword%' ";
10038
        }
10039
10040
        $sql_quiz = "SELECT * FROM $tbl_quiz
10041
                     WHERE
10042
                            c_id = $course_id AND
10043
                            $activeCondition
10044
                            $condition_session
10045
                            $categoryCondition
10046
                            $keywordCondition
10047
                     ORDER BY title ASC";
10048
        $res_quiz = Database::query($sql_quiz);
10049
10050
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
10051
10052
        // Create a search-box
10053
        $form = new FormValidator('search_simple', 'get', $currentUrl);
10054
        $form->addHidden('action', 'add_item');
10055
        $form->addHidden('type', 'step');
10056
        $form->addHidden('lp_id', $this->lp_id);
10057
        $form->addHidden('lp_build_selected', '2');
10058
10059
        $form->addCourseHiddenParams();
10060
        $form->addText(
10061
            'keyword',
10062
            get_lang('Search'),
10063
            false,
10064
            [
10065
                'aria-label' => get_lang('Search'),
10066
            ]
10067
        );
10068
10069
        if (api_get_configuration_value('allow_exercise_categories')) {
10070
            $manager = new ExerciseCategoryManager();
10071
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
10072
            if (!empty($options)) {
10073
                $form->addSelect(
10074
                    'category_id',
10075
                    get_lang('Category'),
10076
                    $options,
10077
                    ['placeholder' => get_lang('Please select an option')]
10078
                );
10079
            }
10080
        }
10081
10082
        $form->addButtonSearch(get_lang('Search'));
10083
        $return = $form->returnForm();
10084
10085
        $return .= '<ul class="lp_resource">';
10086
        $return .= '<li class="lp_resource_element">';
10087
        $return .= Display::return_icon('new_exercice.png');
10088
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
10089
            get_lang('New test').'</a>';
10090
        $return .= '</li>';
10091
10092
        $previewIcon = Display::return_icon(
10093
            'preview_view.png',
10094
            get_lang('Preview')
10095
        );
10096
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
10097
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10098
10099
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
10100
        $repo = Container::getExerciseRepository();
10101
        $courseEntity = api_get_course_entity();
10102
        $sessionEntity = api_get_session_entity();
10103
        while ($row_quiz = Database::fetch_array($res_quiz)) {
10104
            /** @var CQuiz $exercise */
10105
            $exercise = $repo->find($row_quiz['id']);
10106
            $title = strip_tags(
10107
                api_html_entity_decode($row_quiz['title'])
10108
            );
10109
10110
            $visibility = $exercise->isVisible($courseEntity, $sessionEntity);
10111
            /*$visibility = api_get_item_visibility(
10112
                ['real_id' => $course_id],
10113
                TOOL_QUIZ,
10114
                $row_quiz['iid'],
10115
                $session_id
10116
            );*/
10117
10118
            $link = Display::url(
10119
                $previewIcon,
10120
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
10121
                ['target' => '_blank']
10122
            );
10123
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
10124
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
10125
            $return .= $quizIcon;
10126
            $sessionStar = api_get_session_image(
10127
                $row_quiz['session_id'],
10128
                $userInfo['status']
10129
            );
10130
            $return .= Display::url(
10131
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
10132
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['id'].'&lp_id='.$this->lp_id,
10133
                [
10134
                    'class' => $visibility === false ? 'moved text-muted' : 'moved',
10135
                ]
10136
            );
10137
            $return .= '</li>';
10138
        }
10139
10140
        $return .= '</ul>';
10141
10142
        return $return;
10143
    }
10144
10145
    /**
10146
     * Creates a list with all the links in it.
10147
     *
10148
     * @return string
10149
     */
10150
    public function get_links()
10151
    {
10152
        $sessionId = api_get_session_id();
10153
        $repo = Container::getLinkRepository();
10154
10155
        $course = api_get_course_entity();
10156
        $session = api_get_session_entity($sessionId);
10157
        $qb = $repo->getResourcesByCourse($course, $session);
10158
        /** @var CLink[] $links */
10159
        $links = $qb->getQuery()->getResult();
10160
10161
        $selfUrl = api_get_self();
10162
        $courseIdReq = api_get_cidreq();
10163
        $userInfo = api_get_user_info();
10164
10165
        $moveEverywhereIcon = Display::return_icon(
10166
            'move_everywhere.png',
10167
            get_lang('Move'),
10168
            [],
10169
            ICON_SIZE_TINY
10170
        );
10171
10172
        /*$condition_session = api_get_session_condition(
10173
            $session_id,
10174
            true,
10175
            true,
10176
            'link.session_id'
10177
        );
10178
10179
        $sql = "SELECT
10180
                    link.id as link_id,
10181
                    link.title as link_title,
10182
                    link.session_id as link_session_id,
10183
                    link.category_id as category_id,
10184
                    link_category.category_title as category_title
10185
                FROM $tbl_link as link
10186
                LEFT JOIN $linkCategoryTable as link_category
10187
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
10188
                WHERE link.c_id = $course_id $condition_session
10189
                ORDER BY link_category.category_title ASC, link.title ASC";
10190
        $result = Database::query($sql);*/
10191
        $categorizedLinks = [];
10192
        $categories = [];
10193
10194
        foreach ($links as $link) {
10195
            $categoryId = $link->getCategory() !== null ? $link->getCategory()->getIid() : 0;
10196
10197
            if (empty($categoryId)) {
10198
                $categories[0] = get_lang('Uncategorized');
10199
            } else {
10200
                $category = $link->getCategory();
10201
                $categories[$categoryId] = $category->getCategoryTitle();
10202
            }
10203
            $categorizedLinks[$categoryId][$link->getIid()] = $link;
10204
        }
10205
10206
        $linksHtmlCode =
10207
            '<script>
10208
            function toggle_tool(tool, id) {
10209
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
10210
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
10211
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10212
                } else {
10213
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
10214
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
10215
                }
10216
            }
10217
        </script>
10218
10219
        <ul class="lp_resource">
10220
            <li class="lp_resource_element">
10221
                '.Display::return_icon('linksnew.gif').'
10222
                <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').'">'.
10223
                get_lang('Add a link').'
10224
                </a>
10225
            </li>';
10226
10227
        foreach ($categorizedLinks as $categoryId => $links) {
10228
            $linkNodes = null;
10229
            /** @var CLink $link */
10230
            foreach ($links as $key => $link) {
10231
                $title = $link->getTitle();
10232
                $linkSessionId = $link->getSessionId();
10233
10234
                $linkUrl = Display::url(
10235
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10236
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
10237
                    ['target' => '_blank']
10238
                );
10239
10240
                if ($link->isVisible($course, $session)) {
10241
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
10242
                    $linkNodes .=
10243
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
10244
                        <a class="moved" href="#">'.
10245
                            $moveEverywhereIcon.
10246
                        '</a>
10247
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
10248
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
10249
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
10250
                        Security::remove_XSS($title).$sessionStar.$linkUrl.
10251
                        '</a>
10252
                    </li>';
10253
                }
10254
            }
10255
            $linksHtmlCode .=
10256
                '<li>
10257
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
10258
                    <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
10259
                    align="absbottom" />
10260
                </a>
10261
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
10262
            </li>
10263
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
10264
        }
10265
        $linksHtmlCode .= '</ul>';
10266
10267
        return $linksHtmlCode;
10268
    }
10269
10270
    /**
10271
     * Creates a list with all the student publications in it.
10272
     *
10273
     * @return string
10274
     */
10275
    public function get_student_publications()
10276
    {
10277
        $return = '<ul class="lp_resource">';
10278
        $return .= '<li class="lp_resource_element">';
10279
        $return .= Display::return_icon('works_new.gif');
10280
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
10281
            get_lang('Add the Assignments tool to the course').'</a>';
10282
        $return .= '</li>';
10283
10284
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
10285
        $works = getWorkListTeacher(0, 100, null, null, null);
10286
        if (!empty($works)) {
10287
            foreach ($works as $work) {
10288
                $link = Display::url(
10289
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10290
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
10291
                    ['target' => '_blank']
10292
                );
10293
10294
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
10295
                $return .= '<a class="moved" href="#">';
10296
                $return .= Display::return_icon(
10297
                    'move_everywhere.png',
10298
                    get_lang('Move'),
10299
                    [],
10300
                    ICON_SIZE_TINY
10301
                );
10302
                $return .= '</a> ';
10303
10304
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
10305
                $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.'">'.
10306
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
10307
                </a>';
10308
10309
                $return .= '</li>';
10310
            }
10311
        }
10312
10313
        $return .= '</ul>';
10314
10315
        return $return;
10316
    }
10317
10318
    /**
10319
     * Creates a list with all the forums in it.
10320
     *
10321
     * @return string
10322
     */
10323
    public function get_forums()
10324
    {
10325
        require_once '../forum/forumfunction.inc.php';
10326
10327
        $forumCategories = get_forum_categories();
10328
        $forumsInNoCategory = get_forums_in_category(0);
10329
        if (!empty($forumsInNoCategory)) {
10330
            $forumCategories = array_merge(
10331
                $forumCategories,
10332
                [
10333
                    [
10334
                        'cat_id' => 0,
10335
                        'session_id' => 0,
10336
                        'visibility' => 1,
10337
                        'cat_comment' => null,
10338
                    ],
10339
                ]
10340
            );
10341
        }
10342
10343
        $forumList = get_forums();
10344
        $a_forums = [];
10345
        foreach ($forumCategories as $forumCategory) {
10346
            // The forums in this category.
10347
            $forumsInCategory = get_forums_in_category($forumCategory->getIid());
10348
            if (!empty($forumsInCategory)) {
10349
                foreach ($forumList as $forum) {
10350
                    $a_forums[] = $forum;
10351
                }
10352
            }
10353
        }
10354
10355
        $return = '<ul class="lp_resource">';
10356
10357
        // First add link
10358
        $return .= '<li class="lp_resource_element">';
10359
        $return .= Display::return_icon('new_forum.png');
10360
        $return .= Display::url(
10361
            get_lang('Create a new forum'),
10362
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
10363
                'action' => 'add',
10364
                'content' => 'forum',
10365
                'lp_id' => $this->lp_id,
10366
            ]),
10367
            ['title' => get_lang('Create a new forum')]
10368
        );
10369
        $return .= '</li>';
10370
10371
        $return .= '<script>
10372
            function toggle_forum(forum_id) {
10373
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
10374
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
10375
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10376
                } else {
10377
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
10378
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
10379
                }
10380
            }
10381
        </script>';
10382
10383
        foreach ($a_forums as $forum) {
10384
            $forumId = $forum->getIid();
10385
            $title = Security::remove_XSS($forum->getForumTitle());
10386
            $link = Display::url(
10387
                Display::return_icon('preview_view.png', get_lang('Preview')),
10388
                api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forumId,
10389
                ['target' => '_blank']
10390
            );
10391
10392
            $return .= '<li class="lp_resource_element" data_id="'.$forumId.'" data_type="'.TOOL_FORUM.'" title="'.$title.'" >';
10393
            $return .= '<a class="moved" href="#">';
10394
            $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10395
            $return .= ' </a>';
10396
            $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
10397
            $return .= '<a onclick="javascript:toggle_forum('.$forumId.');" style="cursor:hand; vertical-align:middle">
10398
                            <img src="'.Display::returnIconPath('add.png').'" id="forum_'.$forumId.'_opener" align="absbottom" />
10399
                        </a>
10400
                        <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">'.
10401
                $title.' '.$link.'</a>';
10402
10403
            $return .= '</li>';
10404
10405
            $return .= '<div style="display:none" id="forum_'.$forumId.'_content">';
10406
            $a_threads = get_threads($forumId);
10407
            if (is_array($a_threads)) {
10408
                foreach ($a_threads as $thread) {
10409
                    $threadId = $thread->getIid();
10410
                    $link = Display::url(
10411
                        Display::return_icon('preview_view.png', get_lang('Preview')),
10412
                        api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forumId.'&thread='.$threadId,
10413
                        ['target' => '_blank']
10414
                    );
10415
10416
                    $return .= '<li class="lp_resource_element" data_id="'.$thread->getIid().'" data_type="'.TOOL_THREAD.'" title="'.$thread->getThreadTitle().'" >';
10417
                    $return .= '&nbsp;<a class="moved" href="#">';
10418
                    $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10419
                    $return .= ' </a>';
10420
                    $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
10421
                    $return .= '<a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_THREAD.'&thread_id='.$threadId.'&lp_id='.$this->lp_id.'">'.
10422
                        Security::remove_XSS($thread->getThreadTitle()).' '.$link.'</a>';
10423
                    $return .= '</li>';
10424
                }
10425
            }
10426
            $return .= '</div>';
10427
        }
10428
        $return .= '</ul>';
10429
10430
        return $return;
10431
    }
10432
10433
    /**
10434
     * // TODO: The output encoding should be equal to the system encoding.
10435
     *
10436
     * Exports the learning path as a SCORM package. This is the main function that
10437
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
10438
     * whole thing and returns the zip.
10439
     *
10440
     * This method needs to be called in PHP5, as it will fail with non-adequate
10441
     * XML package (like the ones for PHP4), and it is *not* a static method, so
10442
     * you need to call it on a learnpath object.
10443
     *
10444
     * @TODO The method might be redefined later on in the scorm class itself to avoid
10445
     * creating a SCORM structure if there is one already. However, if the initial SCORM
10446
     * path has been modified, it should use the generic method here below.
10447
     *
10448
     * @return string Returns the zip package string, or null if error
10449
     */
10450
    public function scormExport()
10451
    {
10452
        api_set_more_memory_and_time_limits();
10453
10454
        $_course = api_get_course_info();
10455
        $course_id = $_course['real_id'];
10456
        // Create the zip handler (this will remain available throughout the method).
10457
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
10458
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
10459
        $temp_dir_short = uniqid('scorm_export', true);
10460
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
10461
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
10462
        $zip_folder = new PclZip($temp_zip_file);
10463
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
10464
        $root_path = $main_path = api_get_path(SYS_PATH);
10465
        $files_cleanup = [];
10466
10467
        // Place to temporarily stash the zip file.
10468
        // create the temp dir if it doesn't exist
10469
        // or do a cleanup before creating the zip file.
10470
        if (!is_dir($temp_zip_dir)) {
10471
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
10472
        } else {
10473
            // Cleanup: Check the temp dir for old files and delete them.
10474
            $handle = opendir($temp_zip_dir);
10475
            while (false !== ($file = readdir($handle))) {
10476
                if ($file != '.' && $file != '..') {
10477
                    unlink("$temp_zip_dir/$file");
10478
                }
10479
            }
10480
            closedir($handle);
10481
        }
10482
        $zip_files = $zip_files_abs = $zip_files_dist = [];
10483
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
10484
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
10485
        ) {
10486
            // Remove the possible . at the end of the path.
10487
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
10488
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
10489
            mkdir(
10490
                $dest_path_to_scorm_folder,
10491
                api_get_permissions_for_new_directories(),
10492
                true
10493
            );
10494
            copyr(
10495
                $current_course_path.'/scorm/'.$this->path,
10496
                $dest_path_to_scorm_folder,
10497
                ['imsmanifest'],
10498
                $zip_files
10499
            );
10500
        }
10501
10502
        // Build a dummy imsmanifest structure.
10503
        // Do not add to the zip yet (we still need it).
10504
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
10505
        // Aggregation Model official document, section "2.3 Content Packaging".
10506
        // We are going to build a UTF-8 encoded manifest.
10507
        // Later we will recode it to the desired (and supported) encoding.
10508
        $xmldoc = new DOMDocument('1.0');
10509
        $root = $xmldoc->createElement('manifest');
10510
        $root->setAttribute('identifier', 'SingleCourseManifest');
10511
        $root->setAttribute('version', '1.1');
10512
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
10513
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
10514
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
10515
        $root->setAttribute(
10516
            'xsi:schemaLocation',
10517
            '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'
10518
        );
10519
        // Build mandatory sub-root container elements.
10520
        $metadata = $xmldoc->createElement('metadata');
10521
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
10522
        $metadata->appendChild($md_schema);
10523
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
10524
        $metadata->appendChild($md_schemaversion);
10525
        $root->appendChild($metadata);
10526
10527
        $organizations = $xmldoc->createElement('organizations');
10528
        $resources = $xmldoc->createElement('resources');
10529
10530
        // Build the only organization we will use in building our learnpaths.
10531
        $organizations->setAttribute('default', 'chamilo_scorm_export');
10532
        $organization = $xmldoc->createElement('organization');
10533
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
10534
        // To set the title of the SCORM entity (=organization), we take the name given
10535
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
10536
        // learning path charset) as it is the encoding that defines how it is stored
10537
        // in the database. Then we convert it to HTML entities again as the "&" character
10538
        // alone is not authorized in XML (must be &amp;).
10539
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
10540
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
10541
        $organization->appendChild($org_title);
10542
        $folder_name = 'document';
10543
10544
        // Removes the learning_path/scorm_folder path when exporting see #4841
10545
        $path_to_remove = '';
10546
        $path_to_replace = '';
10547
        $result = $this->generate_lp_folder($_course);
10548
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
10549
            $path_to_remove = 'document'.$result['dir'];
10550
            $path_to_replace = $folder_name.'/';
10551
        }
10552
10553
        // Fixes chamilo scorm exports
10554
        if ($this->ref === 'chamilo_scorm_export') {
10555
            $path_to_remove = 'scorm/'.$this->path.'/document/';
10556
        }
10557
10558
        // For each element, add it to the imsmanifest structure, then add it to the zip.
10559
        $link_updates = [];
10560
        $links_to_create = [];
10561
        foreach ($this->ordered_items as $index => $itemId) {
10562
            /** @var learnpathItem $item */
10563
            $item = $this->items[$itemId];
10564
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
10565
                // Get included documents from this item.
10566
                if ($item->type === 'sco') {
10567
                    $inc_docs = $item->get_resources_from_source(
10568
                        null,
10569
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
10570
                    );
10571
                } else {
10572
                    $inc_docs = $item->get_resources_from_source();
10573
                }
10574
10575
                // Give a child element <item> to the <organization> element.
10576
                $my_item_id = $item->get_id();
10577
                $my_item = $xmldoc->createElement('item');
10578
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
10579
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
10580
                $my_item->setAttribute('isvisible', 'true');
10581
                // Give a child element <title> to the <item> element.
10582
                $my_title = $xmldoc->createElement(
10583
                    'title',
10584
                    htmlspecialchars(
10585
                        api_utf8_encode($item->get_title()),
10586
                        ENT_QUOTES,
10587
                        'UTF-8'
10588
                    )
10589
                );
10590
                $my_item->appendChild($my_title);
10591
                // Give a child element <adlcp:prerequisites> to the <item> element.
10592
                $my_prereqs = $xmldoc->createElement(
10593
                    'adlcp:prerequisites',
10594
                    $this->get_scorm_prereq_string($my_item_id)
10595
                );
10596
                $my_prereqs->setAttribute('type', 'aicc_script');
10597
                $my_item->appendChild($my_prereqs);
10598
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
10599
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
10600
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
10601
                //$xmldoc->createElement('adlcp:timelimitaction','');
10602
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
10603
                //$xmldoc->createElement('adlcp:datafromlms','');
10604
                // Give a child element <adlcp:masteryscore> to the <item> element.
10605
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10606
                $my_item->appendChild($my_masteryscore);
10607
10608
                // Attach this item to the organization element or hits parent if there is one.
10609
                if (!empty($item->parent) && $item->parent != 0) {
10610
                    $children = $organization->childNodes;
10611
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
10612
                    if (is_object($possible_parent)) {
10613
                        $possible_parent->appendChild($my_item);
10614
                    } else {
10615
                        if ($this->debug > 0) {
10616
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
10617
                        }
10618
                    }
10619
                } else {
10620
                    if ($this->debug > 0) {
10621
                        error_log('No parent');
10622
                    }
10623
                    $organization->appendChild($my_item);
10624
                }
10625
10626
                // Get the path of the file(s) from the course directory root.
10627
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
10628
                $my_xml_file_path = $my_file_path;
10629
                if (!empty($path_to_remove)) {
10630
                    // From docs
10631
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
10632
10633
                    // From quiz
10634
                    if ($this->ref === 'chamilo_scorm_export') {
10635
                        $path_to_remove = 'scorm/'.$this->path.'/';
10636
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
10637
                    }
10638
                }
10639
10640
                $my_sub_dir = dirname($my_file_path);
10641
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10642
                $my_xml_sub_dir = $my_sub_dir;
10643
                // Give a <resource> child to the <resources> element
10644
                $my_resource = $xmldoc->createElement('resource');
10645
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10646
                $my_resource->setAttribute('type', 'webcontent');
10647
                $my_resource->setAttribute('href', $my_xml_file_path);
10648
                // adlcp:scormtype can be either 'sco' or 'asset'.
10649
                if ($item->type === 'sco') {
10650
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
10651
                } else {
10652
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
10653
                }
10654
                // xml:base is the base directory to find the files declared in this resource.
10655
                $my_resource->setAttribute('xml:base', '');
10656
                // Give a <file> child to the <resource> element.
10657
                $my_file = $xmldoc->createElement('file');
10658
                $my_file->setAttribute('href', $my_xml_file_path);
10659
                $my_resource->appendChild($my_file);
10660
10661
                // Dependency to other files - not yet supported.
10662
                $i = 1;
10663
                if ($inc_docs) {
10664
                    foreach ($inc_docs as $doc_info) {
10665
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
10666
                            continue;
10667
                        }
10668
                        $my_dep = $xmldoc->createElement('resource');
10669
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
10670
                        $my_dep->setAttribute('identifier', $res_id);
10671
                        $my_dep->setAttribute('type', 'webcontent');
10672
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
10673
                        $my_dep_file = $xmldoc->createElement('file');
10674
                        // Check type of URL.
10675
                        if ($doc_info[1] == 'remote') {
10676
                            // Remote file. Save url as is.
10677
                            $my_dep_file->setAttribute('href', $doc_info[0]);
10678
                            $my_dep->setAttribute('xml:base', '');
10679
                        } elseif ($doc_info[1] === 'local') {
10680
                            switch ($doc_info[2]) {
10681
                                case 'url':
10682
                                    // Local URL - save path as url for now, don't zip file.
10683
                                    $abs_path = api_get_path(SYS_PATH).
10684
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10685
                                    $current_dir = dirname($abs_path);
10686
                                    $current_dir = str_replace('\\', '/', $current_dir);
10687
                                    $file_path = realpath($abs_path);
10688
                                    $file_path = str_replace('\\', '/', $file_path);
10689
                                    $my_dep_file->setAttribute('href', $file_path);
10690
                                    $my_dep->setAttribute('xml:base', '');
10691
                                    if (strstr($file_path, $main_path) !== false) {
10692
                                        // The calculated real path is really inside Chamilo's root path.
10693
                                        // Reduce file path to what's under the DocumentRoot.
10694
                                        $replace = $file_path;
10695
                                        $file_path = substr($file_path, strlen($root_path) - 1);
10696
                                        $destinationFile = $file_path;
10697
10698
                                        if (strstr($file_path, 'upload/users') !== false) {
10699
                                            $pos = strpos($file_path, 'my_files/');
10700
                                            if ($pos !== false) {
10701
                                                $onlyDirectory = str_replace(
10702
                                                    'upload/users/',
10703
                                                    '',
10704
                                                    substr($file_path, $pos, strlen($file_path))
10705
                                                );
10706
                                            }
10707
                                            $replace = $onlyDirectory;
10708
                                            $destinationFile = $replace;
10709
                                        }
10710
                                        $zip_files_abs[] = $file_path;
10711
                                        $link_updates[$my_file_path][] = [
10712
                                            'orig' => $doc_info[0],
10713
                                            'dest' => $destinationFile,
10714
                                            'replace' => $replace,
10715
                                        ];
10716
                                        $my_dep_file->setAttribute('href', $file_path);
10717
                                        $my_dep->setAttribute('xml:base', '');
10718
                                    } elseif (empty($file_path)) {
10719
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
10720
                                        $file_path = str_replace('//', '/', $file_path);
10721
                                        if (file_exists($file_path)) {
10722
                                            // We get the relative path.
10723
                                            $file_path = substr($file_path, strlen($current_dir));
10724
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
10725
                                            $link_updates[$my_file_path][] = [
10726
                                                'orig' => $doc_info[0],
10727
                                                'dest' => $file_path,
10728
                                            ];
10729
                                            $my_dep_file->setAttribute('href', $file_path);
10730
                                            $my_dep->setAttribute('xml:base', '');
10731
                                        }
10732
                                    }
10733
                                    break;
10734
                                case 'abs':
10735
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
10736
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
10737
                                    $my_dep->setAttribute('xml:base', '');
10738
10739
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
10740
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
10741
                                    $abs_img_path_without_subdir = $doc_info[0];
10742
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
10743
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
10744
                                    if ($pos === 0) {
10745
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
10746
                                    }
10747
10748
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
10749
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
10750
10751
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
10752
                                    $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
10753
                                    // Check if the current document is in that path.
10754
                                    if (strstr($file_path, $cur_path) !== false) {
10755
                                        $destinationFile = substr($file_path, strlen($cur_path));
10756
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
10757
10758
                                        $fileToTest = $cur_path.$my_file_path;
10759
                                        if (!empty($path_to_remove)) {
10760
                                            $fileToTest = str_replace(
10761
                                                $path_to_remove.'/',
10762
                                                $path_to_replace,
10763
                                                $cur_path.$my_file_path
10764
                                            );
10765
                                        }
10766
10767
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
10768
10769
                                        // Put the current document in the zip (this array is the array
10770
                                        // that will manage documents already in the course folder - relative).
10771
                                        $zip_files[] = $filePathNoCoursePart;
10772
                                        // Update the links to the current document in the
10773
                                        // containing document (make them relative).
10774
                                        $link_updates[$my_file_path][] = [
10775
                                            'orig' => $doc_info[0],
10776
                                            'dest' => $destinationFile,
10777
                                            'replace' => $relative_path,
10778
                                        ];
10779
10780
                                        $my_dep_file->setAttribute('href', $file_path);
10781
                                        $my_dep->setAttribute('xml:base', '');
10782
                                    } elseif (strstr($file_path, $main_path) !== false) {
10783
                                        // The calculated real path is really inside Chamilo's root path.
10784
                                        // Reduce file path to what's under the DocumentRoot.
10785
                                        $file_path = substr($file_path, strlen($root_path));
10786
                                        $zip_files_abs[] = $file_path;
10787
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10788
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
10789
                                        $my_dep->setAttribute('xml:base', '');
10790
                                    } elseif (empty($file_path)) {
10791
                                        // Probably this is an image inside "/main" directory
10792
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
10793
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10794
10795
                                        if (file_exists($file_path)) {
10796
                                            if (strstr($file_path, 'main/default_course_document') !== false) {
10797
                                                // We get the relative path.
10798
                                                $pos = strpos($file_path, 'main/default_course_document/');
10799
                                                if ($pos !== false) {
10800
                                                    $onlyDirectory = str_replace(
10801
                                                        'main/default_course_document/',
10802
                                                        '',
10803
                                                        substr($file_path, $pos, strlen($file_path))
10804
                                                    );
10805
                                                }
10806
10807
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
10808
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
10809
                                                $zip_files_abs[] = $fileAbs;
10810
                                                $link_updates[$my_file_path][] = [
10811
                                                    'orig' => $doc_info[0],
10812
                                                    'dest' => $destinationFile,
10813
                                                ];
10814
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
10815
                                                $my_dep->setAttribute('xml:base', '');
10816
                                            }
10817
                                        }
10818
                                    }
10819
                                    break;
10820
                                case 'rel':
10821
                                    // Path relative to the current document.
10822
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
10823
                                    if (substr($doc_info[0], 0, 2) === '..') {
10824
                                        // Relative path going up.
10825
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
10826
                                        $current_dir = str_replace('\\', '/', $current_dir);
10827
                                        $file_path = realpath($current_dir.$doc_info[0]);
10828
                                        $file_path = str_replace('\\', '/', $file_path);
10829
                                        if (strstr($file_path, $main_path) !== false) {
10830
                                            // The calculated real path is really inside Chamilo's root path.
10831
                                            // Reduce file path to what's under the DocumentRoot.
10832
                                            $file_path = substr($file_path, strlen($root_path));
10833
                                            $zip_files_abs[] = $file_path;
10834
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10835
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
10836
                                            $my_dep->setAttribute('xml:base', '');
10837
                                        }
10838
                                    } else {
10839
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
10840
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
10841
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
10842
                                    }
10843
                                    break;
10844
                                default:
10845
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
10846
                                    $my_dep->setAttribute('xml:base', '');
10847
                                    break;
10848
                            }
10849
                        }
10850
                        $my_dep->appendChild($my_dep_file);
10851
                        $resources->appendChild($my_dep);
10852
                        $dependency = $xmldoc->createElement('dependency');
10853
                        $dependency->setAttribute('identifierref', $res_id);
10854
                        $my_resource->appendChild($dependency);
10855
                        $i++;
10856
                    }
10857
                }
10858
                $resources->appendChild($my_resource);
10859
                $zip_files[] = $my_file_path;
10860
            } else {
10861
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
10862
                switch ($item->type) {
10863
                    case TOOL_LINK:
10864
                        $my_item = $xmldoc->createElement('item');
10865
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
10866
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
10867
                        $my_item->setAttribute('isvisible', 'true');
10868
                        // Give a child element <title> to the <item> element.
10869
                        $my_title = $xmldoc->createElement(
10870
                            'title',
10871
                            htmlspecialchars(
10872
                                api_utf8_encode($item->get_title()),
10873
                                ENT_QUOTES,
10874
                                'UTF-8'
10875
                            )
10876
                        );
10877
                        $my_item->appendChild($my_title);
10878
                        // Give a child element <adlcp:prerequisites> to the <item> element.
10879
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
10880
                        $my_prereqs->setAttribute('type', 'aicc_script');
10881
                        $my_item->appendChild($my_prereqs);
10882
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
10883
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
10884
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
10885
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
10886
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
10887
                        //$xmldoc->createElement('adlcp:datafromlms', '');
10888
                        // Give a child element <adlcp:masteryscore> to the <item> element.
10889
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10890
                        $my_item->appendChild($my_masteryscore);
10891
10892
                        // Attach this item to the organization element or its parent if there is one.
10893
                        if (!empty($item->parent) && $item->parent != 0) {
10894
                            $children = $organization->childNodes;
10895
                            for ($i = 0; $i < $children->length; $i++) {
10896
                                $item_temp = $children->item($i);
10897
                                if ($item_temp->nodeName == 'item') {
10898
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
10899
                                        $item_temp->appendChild($my_item);
10900
                                    }
10901
                                }
10902
                            }
10903
                        } else {
10904
                            $organization->appendChild($my_item);
10905
                        }
10906
10907
                        $my_file_path = 'link_'.$item->get_id().'.html';
10908
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
10909
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
10910
                        $rs = Database::query($sql);
10911
                        if ($link = Database::fetch_array($rs)) {
10912
                            $url = $link['url'];
10913
                            $title = stripslashes($link['title']);
10914
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
10915
                            $my_xml_file_path = $my_file_path;
10916
                            $my_sub_dir = dirname($my_file_path);
10917
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10918
                            $my_xml_sub_dir = $my_sub_dir;
10919
                            // Give a <resource> child to the <resources> element.
10920
                            $my_resource = $xmldoc->createElement('resource');
10921
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10922
                            $my_resource->setAttribute('type', 'webcontent');
10923
                            $my_resource->setAttribute('href', $my_xml_file_path);
10924
                            // adlcp:scormtype can be either 'sco' or 'asset'.
10925
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
10926
                            // xml:base is the base directory to find the files declared in this resource.
10927
                            $my_resource->setAttribute('xml:base', '');
10928
                            // give a <file> child to the <resource> element.
10929
                            $my_file = $xmldoc->createElement('file');
10930
                            $my_file->setAttribute('href', $my_xml_file_path);
10931
                            $my_resource->appendChild($my_file);
10932
                            $resources->appendChild($my_resource);
10933
                        }
10934
                        break;
10935
                    case TOOL_QUIZ:
10936
                        $exe_id = $item->path;
10937
                        // Should be using ref when everything will be cleaned up in this regard.
10938
                        $exe = new Exercise();
10939
                        $exe->read($exe_id);
10940
                        $my_item = $xmldoc->createElement('item');
10941
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
10942
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
10943
                        $my_item->setAttribute('isvisible', 'true');
10944
                        // Give a child element <title> to the <item> element.
10945
                        $my_title = $xmldoc->createElement(
10946
                            'title',
10947
                            htmlspecialchars(
10948
                                api_utf8_encode($item->get_title()),
10949
                                ENT_QUOTES,
10950
                                'UTF-8'
10951
                            )
10952
                        );
10953
                        $my_item->appendChild($my_title);
10954
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
10955
                        $my_item->appendChild($my_max_score);
10956
                        // Give a child element <adlcp:prerequisites> to the <item> element.
10957
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
10958
                        $my_prereqs->setAttribute('type', 'aicc_script');
10959
                        $my_item->appendChild($my_prereqs);
10960
                        // Give a child element <adlcp:masteryscore> to the <item> element.
10961
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10962
                        $my_item->appendChild($my_masteryscore);
10963
10964
                        // Attach this item to the organization element or hits parent if there is one.
10965
                        if (!empty($item->parent) && $item->parent != 0) {
10966
                            $children = $organization->childNodes;
10967
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
10968
                            if ($possible_parent) {
10969
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
10970
                                    $possible_parent->appendChild($my_item);
10971
                                }
10972
                            }
10973
                        } else {
10974
                            $organization->appendChild($my_item);
10975
                        }
10976
10977
                        // Get the path of the file(s) from the course directory root
10978
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
10979
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
10980
                        // Write the contents of the exported exercise into a (big) html file
10981
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
10982
                        $scormExercise = new ScormExercise($exe, true);
10983
                        $contents = $scormExercise->export();
10984
10985
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
10986
                        $res = file_put_contents($tmp_file_path, $contents);
10987
                        if ($res === false) {
10988
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
10989
                        }
10990
                        $files_cleanup[] = $tmp_file_path;
10991
                        $my_xml_file_path = $my_file_path;
10992
                        $my_sub_dir = dirname($my_file_path);
10993
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10994
                        $my_xml_sub_dir = $my_sub_dir;
10995
                        // Give a <resource> child to the <resources> element.
10996
                        $my_resource = $xmldoc->createElement('resource');
10997
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10998
                        $my_resource->setAttribute('type', 'webcontent');
10999
                        $my_resource->setAttribute('href', $my_xml_file_path);
11000
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11001
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
11002
                        // xml:base is the base directory to find the files declared in this resource.
11003
                        $my_resource->setAttribute('xml:base', '');
11004
                        // Give a <file> child to the <resource> element.
11005
                        $my_file = $xmldoc->createElement('file');
11006
                        $my_file->setAttribute('href', $my_xml_file_path);
11007
                        $my_resource->appendChild($my_file);
11008
11009
                        // Get included docs.
11010
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
11011
11012
                        // Dependency to other files - not yet supported.
11013
                        $i = 1;
11014
                        foreach ($inc_docs as $doc_info) {
11015
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
11016
                                continue;
11017
                            }
11018
                            $my_dep = $xmldoc->createElement('resource');
11019
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11020
                            $my_dep->setAttribute('identifier', $res_id);
11021
                            $my_dep->setAttribute('type', 'webcontent');
11022
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
11023
                            $my_dep_file = $xmldoc->createElement('file');
11024
                            // Check type of URL.
11025
                            if ($doc_info[1] == 'remote') {
11026
                                // Remote file. Save url as is.
11027
                                $my_dep_file->setAttribute('href', $doc_info[0]);
11028
                                $my_dep->setAttribute('xml:base', '');
11029
                            } elseif ($doc_info[1] == 'local') {
11030
                                switch ($doc_info[2]) {
11031
                                    case 'url': // Local URL - save path as url for now, don't zip file.
11032
                                        // Save file but as local file (retrieve from URL).
11033
                                        $abs_path = api_get_path(SYS_PATH).
11034
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11035
                                        $current_dir = dirname($abs_path);
11036
                                        $current_dir = str_replace('\\', '/', $current_dir);
11037
                                        $file_path = realpath($abs_path);
11038
                                        $file_path = str_replace('\\', '/', $file_path);
11039
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11040
                                        $my_dep->setAttribute('xml:base', '');
11041
                                        if (strstr($file_path, $main_path) !== false) {
11042
                                            // The calculated real path is really inside the chamilo root path.
11043
                                            // Reduce file path to what's under the DocumentRoot.
11044
                                            $file_path = substr($file_path, strlen($root_path));
11045
                                            $zip_files_abs[] = $file_path;
11046
                                            $link_updates[$my_file_path][] = [
11047
                                                'orig' => $doc_info[0],
11048
                                                'dest' => 'document/'.$file_path,
11049
                                            ];
11050
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11051
                                            $my_dep->setAttribute('xml:base', '');
11052
                                        } elseif (empty($file_path)) {
11053
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11054
                                            $file_path = str_replace('//', '/', $file_path);
11055
                                            if (file_exists($file_path)) {
11056
                                                $file_path = substr($file_path, strlen($current_dir));
11057
                                                // We get the relative path.
11058
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11059
                                                $link_updates[$my_file_path][] = [
11060
                                                    'orig' => $doc_info[0],
11061
                                                    'dest' => 'document/'.$file_path,
11062
                                                ];
11063
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11064
                                                $my_dep->setAttribute('xml:base', '');
11065
                                            }
11066
                                        }
11067
                                        break;
11068
                                    case 'abs':
11069
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11070
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11071
                                        $current_dir = str_replace('\\', '/', $current_dir);
11072
                                        $file_path = realpath($doc_info[0]);
11073
                                        $file_path = str_replace('\\', '/', $file_path);
11074
                                        $my_dep_file->setAttribute('href', $file_path);
11075
                                        $my_dep->setAttribute('xml:base', '');
11076
11077
                                        if (strstr($file_path, $main_path) !== false) {
11078
                                            // The calculated real path is really inside the chamilo root path.
11079
                                            // Reduce file path to what's under the DocumentRoot.
11080
                                            $file_path = substr($file_path, strlen($root_path));
11081
                                            $zip_files_abs[] = $file_path;
11082
                                            $link_updates[$my_file_path][] = [
11083
                                                'orig' => $doc_info[0],
11084
                                                'dest' => $file_path,
11085
                                            ];
11086
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11087
                                            $my_dep->setAttribute('xml:base', '');
11088
                                        } elseif (empty($file_path)) {
11089
                                            $docSysPartPath = str_replace(
11090
                                                api_get_path(REL_COURSE_PATH),
11091
                                                '',
11092
                                                $doc_info[0]
11093
                                            );
11094
11095
                                            $docSysPartPathNoCourseCode = str_replace(
11096
                                                $_course['directory'].'/',
11097
                                                '',
11098
                                                $docSysPartPath
11099
                                            );
11100
11101
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
11102
                                            if (file_exists($docSysPath)) {
11103
                                                $file_path = $docSysPartPathNoCourseCode;
11104
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11105
                                                $link_updates[$my_file_path][] = [
11106
                                                    'orig' => $doc_info[0],
11107
                                                    'dest' => $file_path,
11108
                                                ];
11109
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11110
                                                $my_dep->setAttribute('xml:base', '');
11111
                                            }
11112
                                        }
11113
                                        break;
11114
                                    case 'rel':
11115
                                        // Path relative to the current document. Save xml:base as current document's
11116
                                        // directory and save file in zip as subdir.file_path
11117
                                        if (substr($doc_info[0], 0, 2) === '..') {
11118
                                            // Relative path going up.
11119
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11120
                                            $current_dir = str_replace('\\', '/', $current_dir);
11121
                                            $file_path = realpath($current_dir.$doc_info[0]);
11122
                                            $file_path = str_replace('\\', '/', $file_path);
11123
                                            if (strstr($file_path, $main_path) !== false) {
11124
                                                // The calculated real path is really inside Chamilo's root path.
11125
                                                // Reduce file path to what's under the DocumentRoot.
11126
11127
                                                $file_path = substr($file_path, strlen($root_path));
11128
                                                $file_path_dest = $file_path;
11129
11130
                                                // File path is courses/CHAMILO/document/....
11131
                                                $info_file_path = explode('/', $file_path);
11132
                                                if ($info_file_path[0] == 'courses') {
11133
                                                    // Add character "/" in file path.
11134
                                                    $file_path_dest = 'document/'.$file_path;
11135
                                                }
11136
                                                $zip_files_abs[] = $file_path;
11137
11138
                                                $link_updates[$my_file_path][] = [
11139
                                                    'orig' => $doc_info[0],
11140
                                                    'dest' => $file_path_dest,
11141
                                                ];
11142
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11143
                                                $my_dep->setAttribute('xml:base', '');
11144
                                            }
11145
                                        } else {
11146
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11147
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
11148
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11149
                                        }
11150
                                        break;
11151
                                    default:
11152
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
11153
                                        $my_dep->setAttribute('xml:base', '');
11154
                                        break;
11155
                                }
11156
                            }
11157
                            $my_dep->appendChild($my_dep_file);
11158
                            $resources->appendChild($my_dep);
11159
                            $dependency = $xmldoc->createElement('dependency');
11160
                            $dependency->setAttribute('identifierref', $res_id);
11161
                            $my_resource->appendChild($dependency);
11162
                            $i++;
11163
                        }
11164
                        $resources->appendChild($my_resource);
11165
                        $zip_files[] = $my_file_path;
11166
                        break;
11167
                    default:
11168
                        // Get the path of the file(s) from the course directory root
11169
                        $my_file_path = 'non_exportable.html';
11170
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
11171
                        $my_xml_file_path = $my_file_path;
11172
                        $my_sub_dir = dirname($my_file_path);
11173
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11174
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
11175
                        $my_xml_sub_dir = $my_sub_dir;
11176
                        // Give a <resource> child to the <resources> element.
11177
                        $my_resource = $xmldoc->createElement('resource');
11178
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11179
                        $my_resource->setAttribute('type', 'webcontent');
11180
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
11181
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11182
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
11183
                        // xml:base is the base directory to find the files declared in this resource.
11184
                        $my_resource->setAttribute('xml:base', '');
11185
                        // Give a <file> child to the <resource> element.
11186
                        $my_file = $xmldoc->createElement('file');
11187
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
11188
                        $my_resource->appendChild($my_file);
11189
                        $resources->appendChild($my_resource);
11190
                        break;
11191
                }
11192
            }
11193
        }
11194
        $organizations->appendChild($organization);
11195
        $root->appendChild($organizations);
11196
        $root->appendChild($resources);
11197
        $xmldoc->appendChild($root);
11198
11199
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
11200
11201
        // then add the file to the zip, then destroy the file (this is done automatically).
11202
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
11203
        foreach ($zip_files as $file_path) {
11204
            if (empty($file_path)) {
11205
                continue;
11206
            }
11207
11208
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
11209
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
11210
11211
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
11212
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
11213
            }
11214
11215
            $this->create_path($dest_file);
11216
            @copy($filePath, $dest_file);
11217
11218
            // Check if the file needs a link update.
11219
            if (in_array($file_path, array_keys($link_updates))) {
11220
                $string = file_get_contents($dest_file);
11221
                unlink($dest_file);
11222
                foreach ($link_updates[$file_path] as $old_new) {
11223
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11224
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11225
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11226
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11227
                    if (substr($old_new['dest'], -3) === 'flv' &&
11228
                        substr($old_new['dest'], 0, 5) === 'main/'
11229
                    ) {
11230
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11231
                    } elseif (substr($old_new['dest'], -3) === 'flv' &&
11232
                        substr($old_new['dest'], 0, 6) === 'video/'
11233
                    ) {
11234
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
11235
                    }
11236
11237
                    // Fix to avoid problems with default_course_document
11238
                    if (strpos('main/default_course_document', $old_new['dest']) === false) {
11239
                        $newDestination = $old_new['dest'];
11240
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
11241
                            $newDestination = $old_new['replace'];
11242
                        }
11243
                    } else {
11244
                        $newDestination = str_replace('document/', '', $old_new['dest']);
11245
                    }
11246
                    $string = str_replace($old_new['orig'], $newDestination, $string);
11247
11248
                    // Add files inside the HTMLs
11249
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
11250
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
11251
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
11252
                        copy(
11253
                            $sys_course_path.$new_path,
11254
                            $destinationFile
11255
                        );
11256
                    }
11257
                }
11258
                file_put_contents($dest_file, $string);
11259
            }
11260
11261
            if (file_exists($filePath) && $copyAll) {
11262
                $extension = $this->get_extension($filePath);
11263
                if (in_array($extension, ['html', 'html'])) {
11264
                    $containerOrigin = dirname($filePath);
11265
                    $containerDestination = dirname($dest_file);
11266
11267
                    $finder = new Finder();
11268
                    $finder->files()->in($containerOrigin)
11269
                        ->notName('*_DELETED_*')
11270
                        ->exclude('share_folder')
11271
                        ->exclude('chat_files')
11272
                        ->exclude('certificates')
11273
                    ;
11274
11275
                    if (is_dir($containerOrigin) &&
11276
                        is_dir($containerDestination)
11277
                    ) {
11278
                        $fs = new Filesystem();
11279
                        $fs->mirror(
11280
                            $containerOrigin,
11281
                            $containerDestination,
11282
                            $finder
11283
                        );
11284
                    }
11285
                }
11286
            }
11287
        }
11288
11289
        foreach ($zip_files_abs as $file_path) {
11290
            if (empty($file_path)) {
11291
                continue;
11292
            }
11293
11294
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
11295
                continue;
11296
            }
11297
11298
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
11299
            if (strstr($file_path, 'upload/users') !== false) {
11300
                $pos = strpos($file_path, 'my_files/');
11301
                if ($pos !== false) {
11302
                    $onlyDirectory = str_replace(
11303
                        'upload/users/',
11304
                        '',
11305
                        substr($file_path, $pos, strlen($file_path))
11306
                    );
11307
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
11308
                }
11309
            }
11310
11311
            if (strstr($file_path, 'default_course_document/') !== false) {
11312
                $replace = str_replace('/main', '', $file_path);
11313
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
11314
            }
11315
11316
            if (empty($dest_file)) {
11317
                continue;
11318
            }
11319
11320
            $this->create_path($dest_file);
11321
            copy($main_path.$file_path, $dest_file);
11322
            // Check if the file needs a link update.
11323
            if (in_array($file_path, array_keys($link_updates))) {
11324
                $string = file_get_contents($dest_file);
11325
                unlink($dest_file);
11326
                foreach ($link_updates[$file_path] as $old_new) {
11327
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11328
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11329
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11330
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11331
                    if (substr($old_new['dest'], -3) == 'flv' &&
11332
                        substr($old_new['dest'], 0, 5) == 'main/'
11333
                    ) {
11334
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11335
                    }
11336
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
11337
                }
11338
                file_put_contents($dest_file, $string);
11339
            }
11340
        }
11341
11342
        if (is_array($links_to_create)) {
11343
            foreach ($links_to_create as $file => $link) {
11344
                $content = '<!DOCTYPE html><head>
11345
                            <meta charset="'.api_get_language_isocode().'" />
11346
                            <title>'.$link['title'].'</title>
11347
                            </head>
11348
                            <body dir="'.api_get_text_direction().'">
11349
                            <div style="text-align:center">
11350
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
11351
                            </body>
11352
                            </html>';
11353
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
11354
            }
11355
        }
11356
11357
        // Add non exportable message explanation.
11358
        $lang_not_exportable = get_lang('This learning object or activity is not SCORM compliant. That\'s why it is not exportable.');
11359
        $file_content = '<!DOCTYPE html><head>
11360
                        <meta charset="'.api_get_language_isocode().'" />
11361
                        <title>'.$lang_not_exportable.'</title>
11362
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
11363
                        </head>
11364
                        <body dir="'.api_get_text_direction().'">';
11365
        $file_content .=
11366
            <<<EOD
11367
                    <style>
11368
            .error-message {
11369
                font-family: arial, verdana, helvetica, sans-serif;
11370
                border-width: 1px;
11371
                border-style: solid;
11372
                left: 50%;
11373
                margin: 10px auto;
11374
                min-height: 30px;
11375
                padding: 5px;
11376
                right: 50%;
11377
                width: 500px;
11378
                background-color: #FFD1D1;
11379
                border-color: #FF0000;
11380
                color: #000;
11381
            }
11382
        </style>
11383
    <body>
11384
        <div class="error-message">
11385
            $lang_not_exportable
11386
        </div>
11387
    </body>
11388
</html>
11389
EOD;
11390
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
11391
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
11392
        }
11393
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
11394
11395
        // Add the extra files that go along with a SCORM package.
11396
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
11397
11398
        $fs = new Filesystem();
11399
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
11400
11401
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
11402
        $manifest = @$xmldoc->saveXML();
11403
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
11404
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
11405
        $zip_folder->add(
11406
            $archivePath.'/'.$temp_dir_short,
11407
            PCLZIP_OPT_REMOVE_PATH,
11408
            $archivePath.'/'.$temp_dir_short.'/'
11409
        );
11410
11411
        // Clean possible temporary files.
11412
        foreach ($files_cleanup as $file) {
11413
            $res = unlink($file);
11414
            if ($res === false) {
11415
                error_log(
11416
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
11417
                    0
11418
                );
11419
            }
11420
        }
11421
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
11422
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
11423
    }
11424
11425
    /**
11426
     * @param int $lp_id
11427
     *
11428
     * @return bool
11429
     */
11430
    public function scorm_export_to_pdf($lp_id)
11431
    {
11432
        $lp_id = (int) $lp_id;
11433
        $files_to_export = [];
11434
11435
        $sessionId = api_get_session_id();
11436
        $course_data = api_get_course_info($this->cc);
11437
11438
        if (!empty($course_data)) {
11439
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
11440
            $list = self::get_flat_ordered_items_list($lp_id);
11441
            if (!empty($list)) {
11442
                foreach ($list as $item_id) {
11443
                    $item = $this->items[$item_id];
11444
                    switch ($item->type) {
11445
                        case 'document':
11446
                            // Getting documents from a LP with chamilo documents
11447
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
11448
                            // Try loading document from the base course.
11449
                            if (empty($file_data) && !empty($sessionId)) {
11450
                                $file_data = DocumentManager::get_document_data_by_id(
11451
                                    $item->path,
11452
                                    $this->cc,
11453
                                    false,
11454
                                    0
11455
                                );
11456
                            }
11457
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
11458
                            if (file_exists($file_path)) {
11459
                                $files_to_export[] = [
11460
                                    'title' => $item->get_title(),
11461
                                    'path' => $file_path,
11462
                                ];
11463
                            }
11464
                            break;
11465
                        case 'asset': //commes from a scorm package generated by chamilo
11466
                        case 'sco':
11467
                            $file_path = $scorm_path.'/'.$item->path;
11468
                            if (file_exists($file_path)) {
11469
                                $files_to_export[] = [
11470
                                    'title' => $item->get_title(),
11471
                                    'path' => $file_path,
11472
                                ];
11473
                            }
11474
                            break;
11475
                        case 'dir':
11476
                            $files_to_export[] = [
11477
                                'title' => $item->get_title(),
11478
                                'path' => null,
11479
                            ];
11480
                            break;
11481
                    }
11482
                }
11483
            }
11484
11485
            $pdf = new PDF();
11486
            $result = $pdf->html_to_pdf(
11487
                $files_to_export,
11488
                $this->name,
11489
                $this->cc,
11490
                true,
11491
                true,
11492
                true,
11493
                $this->get_name()
11494
            );
11495
11496
            return $result;
11497
        }
11498
11499
        return false;
11500
    }
11501
11502
    /**
11503
     * Temp function to be moved in main_api or the best place around for this.
11504
     * Creates a file path if it doesn't exist.
11505
     *
11506
     * @param string $path
11507
     */
11508
    public function create_path($path)
11509
    {
11510
        $path_bits = explode('/', dirname($path));
11511
11512
        // IS_WINDOWS_OS has been defined in main_api.lib.php
11513
        $path_built = IS_WINDOWS_OS ? '' : '/';
11514
        foreach ($path_bits as $bit) {
11515
            if (!empty($bit)) {
11516
                $new_path = $path_built.$bit;
11517
                if (is_dir($new_path)) {
11518
                    $path_built = $new_path.'/';
11519
                } else {
11520
                    mkdir($new_path, api_get_permissions_for_new_directories());
11521
                    $path_built = $new_path.'/';
11522
                }
11523
            }
11524
        }
11525
    }
11526
11527
    /**
11528
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
11529
     *
11530
     * @return bool The results of the unlink function, or false if there was no image to start with
11531
     */
11532
    public function delete_lp_image()
11533
    {
11534
        $img = $this->get_preview_image();
11535
        if ($img != '') {
11536
            $del_file = $this->get_preview_image_path(null, 'sys');
11537
            if (isset($del_file) && file_exists($del_file)) {
11538
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
11539
                if (file_exists($del_file_2)) {
11540
                    unlink($del_file_2);
11541
                }
11542
                $this->set_preview_image('');
11543
11544
                return @unlink($del_file);
11545
            }
11546
        }
11547
11548
        return false;
11549
    }
11550
11551
    /**
11552
     * Uploads an author image to the upload/learning_path/images directory.
11553
     *
11554
     * @param array    The image array, coming from the $_FILES superglobal
11555
     *
11556
     * @return bool True on success, false on error
11557
     */
11558
    public function upload_image($image_array)
11559
    {
11560
        if (!empty($image_array['name'])) {
11561
            $upload_ok = process_uploaded_file($image_array);
11562
            $has_attachment = true;
11563
        }
11564
11565
        if ($upload_ok && $has_attachment) {
11566
            $courseDir = api_get_course_path().'/upload/learning_path/images';
11567
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
11568
            $updir = $sys_course_path.$courseDir;
11569
            // Try to add an extension to the file if it hasn't one.
11570
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
11571
11572
            if (filter_extension($new_file_name)) {
11573
                $file_extension = explode('.', $image_array['name']);
11574
                $file_extension = strtolower($file_extension[count($file_extension) - 1]);
11575
                $filename = uniqid('');
11576
                $new_file_name = $filename.'.'.$file_extension;
11577
                $new_path = $updir.'/'.$new_file_name;
11578
11579
                // Resize the image.
11580
                $temp = new Image($image_array['tmp_name']);
11581
                $temp->resize(104);
11582
                $result = $temp->send_image($new_path);
11583
11584
                // Storing the image filename.
11585
                if ($result) {
11586
                    $this->set_preview_image($new_file_name);
11587
11588
                    //Resize to 64px to use on course homepage
11589
                    $temp->resize(64);
11590
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
11591
11592
                    return true;
11593
                }
11594
            }
11595
        }
11596
11597
        return false;
11598
    }
11599
11600
    /**
11601
     * @param int    $lp_id
11602
     * @param string $status
11603
     */
11604
    public function set_autolaunch($lp_id, $status)
11605
    {
11606
        $course_id = api_get_course_int_id();
11607
        $lp_id = (int) $lp_id;
11608
        $status = (int) $status;
11609
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
11610
11611
        // Setting everything to autolaunch = 0
11612
        $attributes['autolaunch'] = 0;
11613
        $where = [
11614
            'session_id = ? AND c_id = ? ' => [
11615
                api_get_session_id(),
11616
                $course_id,
11617
            ],
11618
        ];
11619
        Database::update($lp_table, $attributes, $where);
11620
        if ($status == 1) {
11621
            //Setting my lp_id to autolaunch = 1
11622
            $attributes['autolaunch'] = 1;
11623
            $where = [
11624
                'iid = ? AND session_id = ? AND c_id = ?' => [
11625
                    $lp_id,
11626
                    api_get_session_id(),
11627
                    $course_id,
11628
                ],
11629
            ];
11630
            Database::update($lp_table, $attributes, $where);
11631
        }
11632
    }
11633
11634
    /**
11635
     * Gets previous_item_id for the next element of the lp_item table.
11636
     *
11637
     * @author Isaac flores paz
11638
     *
11639
     * @return int Previous item ID
11640
     */
11641
    public function select_previous_item_id()
11642
    {
11643
        $course_id = api_get_course_int_id();
11644
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11645
11646
        // Get the max order of the items
11647
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
11648
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
11649
        $rs_max_order = Database::query($sql);
11650
        $row_max_order = Database::fetch_object($rs_max_order);
11651
        $max_order = $row_max_order->display_order;
11652
        // Get the previous item ID
11653
        $sql = "SELECT iid as previous FROM $table_lp_item
11654
                WHERE
11655
                    c_id = $course_id AND
11656
                    lp_id = ".$this->lp_id." AND
11657
                    display_order = '$max_order' ";
11658
        $rs_max = Database::query($sql);
11659
        $row_max = Database::fetch_object($rs_max);
11660
11661
        // Return the previous item ID
11662
        return $row_max->previous;
11663
    }
11664
11665
    /**
11666
     * Copies an LP.
11667
     */
11668
    public function copy()
11669
    {
11670
        // Course builder
11671
        $cb = new CourseBuilder();
11672
11673
        //Setting tools that will be copied
11674
        $cb->set_tools_to_build(['learnpaths']);
11675
11676
        //Setting elements that will be copied
11677
        $cb->set_tools_specific_id_list(
11678
            ['learnpaths' => [$this->lp_id]]
11679
        );
11680
11681
        $course = $cb->build();
11682
11683
        //Course restorer
11684
        $course_restorer = new CourseRestorer($course);
11685
        $course_restorer->set_add_text_in_items(true);
11686
        $course_restorer->set_tool_copy_settings(
11687
            ['learnpaths' => ['reset_dates' => true]]
11688
        );
11689
        $course_restorer->restore(
11690
            api_get_course_id(),
11691
            api_get_session_id(),
11692
            false,
11693
            false
11694
        );
11695
    }
11696
11697
    /**
11698
     * Verify document size.
11699
     *
11700
     * @param string $s
11701
     *
11702
     * @return bool
11703
     */
11704
    public static function verify_document_size($s)
11705
    {
11706
        $post_max = ini_get('post_max_size');
11707
        if (substr($post_max, -1, 1) == 'M') {
11708
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
11709
        } elseif (substr($post_max, -1, 1) == 'G') {
11710
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
11711
        }
11712
        $upl_max = ini_get('upload_max_filesize');
11713
        if (substr($upl_max, -1, 1) == 'M') {
11714
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
11715
        } elseif (substr($upl_max, -1, 1) == 'G') {
11716
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
11717
        }
11718
11719
        $repo = Container::getDocumentRepository();
11720
        $documents_total_space = $repo->getTotalSpace(api_get_course_int_id());
11721
11722
        $course_max_space = DocumentManager::get_course_quota();
11723
        $total_size = filesize($s) + $documents_total_space;
11724
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
11725
            return true;
11726
        }
11727
11728
        return false;
11729
    }
11730
11731
    /**
11732
     * Clear LP prerequisites.
11733
     */
11734
    public function clear_prerequisites()
11735
    {
11736
        $course_id = $this->get_course_int_id();
11737
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11738
        $lp_id = $this->get_id();
11739
        // Cleaning prerequisites
11740
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
11741
                WHERE c_id = $course_id AND lp_id = $lp_id";
11742
        Database::query($sql);
11743
11744
        // Cleaning mastery score for exercises
11745
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
11746
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
11747
        Database::query($sql);
11748
    }
11749
11750
    public function set_previous_step_as_prerequisite_for_all_items()
11751
    {
11752
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11753
        $course_id = $this->get_course_int_id();
11754
        $lp_id = $this->get_id();
11755
11756
        if (!empty($this->items)) {
11757
            $previous_item_id = null;
11758
            $previous_item_max = 0;
11759
            $previous_item_type = null;
11760
            $last_item_not_dir = null;
11761
            $last_item_not_dir_type = null;
11762
            $last_item_not_dir_max = null;
11763
11764
            foreach ($this->ordered_items as $itemId) {
11765
                $item = $this->getItem($itemId);
11766
                // if there was a previous item... (otherwise jump to set it)
11767
                if (!empty($previous_item_id)) {
11768
                    $current_item_id = $item->get_id(); //save current id
11769
                    if ($item->get_type() != 'dir') {
11770
                        // Current item is not a folder, so it qualifies to get a prerequisites
11771
                        if ($last_item_not_dir_type == 'quiz') {
11772
                            // if previous is quiz, mark its max score as default score to be achieved
11773
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
11774
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
11775
                            Database::query($sql);
11776
                        }
11777
                        // now simply update the prerequisite to set it to the last non-chapter item
11778
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
11779
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
11780
                        Database::query($sql);
11781
                        // record item as 'non-chapter' reference
11782
                        $last_item_not_dir = $item->get_id();
11783
                        $last_item_not_dir_type = $item->get_type();
11784
                        $last_item_not_dir_max = $item->get_max();
11785
                    }
11786
                } else {
11787
                    if ($item->get_type() != 'dir') {
11788
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
11789
                        $last_item_not_dir = $item->get_id();
11790
                        $last_item_not_dir_type = $item->get_type();
11791
                        $last_item_not_dir_max = $item->get_max();
11792
                    }
11793
                }
11794
                // Saving the item as "previous item" for the next loop
11795
                $previous_item_id = $item->get_id();
11796
                $previous_item_max = $item->get_max();
11797
                $previous_item_type = $item->get_type();
11798
            }
11799
        }
11800
    }
11801
11802
    /**
11803
     * @param array $params
11804
     *
11805
     * @throws \Doctrine\ORM\OptimisticLockException
11806
     *
11807
     * @return int
11808
     */
11809
    public static function createCategory($params)
11810
    {
11811
        $item = new CLpCategory();
11812
        $item->setName($params['name']);
11813
        $item->setCId($params['c_id']);
11814
11815
        $repo = Container::getLpCategoryRepository();
11816
        $em = $repo->getEntityManager();
11817
        $em->persist($item);
11818
        $courseEntity = api_get_course_entity(api_get_course_int_id());
11819
11820
        $repo->addResourceToCourse(
11821
            $item,
11822
            ResourceLink::VISIBILITY_PUBLISHED,
11823
            api_get_user_entity(api_get_user_id()),
11824
            $courseEntity,
11825
            api_get_session_entity(),
11826
            api_get_group_entity()
11827
        );
11828
11829
        $em->flush();
11830
11831
        /*api_item_property_update(
11832
            api_get_course_info(),
11833
            TOOL_LEARNPATH_CATEGORY,
11834
            $item->getId(),
11835
            'visible',
11836
            api_get_user_id()
11837
        );*/
11838
11839
        return $item->getId();
11840
    }
11841
11842
    /**
11843
     * @param array $params
11844
     *
11845
     * @throws \Doctrine\ORM\ORMException
11846
     * @throws \Doctrine\ORM\OptimisticLockException
11847
     * @throws \Doctrine\ORM\TransactionRequiredException
11848
     */
11849
    public static function updateCategory($params)
11850
    {
11851
        $em = Database::getManager();
11852
        /** @var CLpCategory $item */
11853
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
11854
        if ($item) {
11855
            $item->setName($params['name']);
11856
            $em->merge($item);
11857
            $em->flush();
11858
        }
11859
    }
11860
11861
    /**
11862
     * @param int $id
11863
     *
11864
     * @throws \Doctrine\ORM\ORMException
11865
     * @throws \Doctrine\ORM\OptimisticLockException
11866
     * @throws \Doctrine\ORM\TransactionRequiredException
11867
     */
11868
    public static function moveUpCategory($id)
11869
    {
11870
        $id = (int) $id;
11871
        $em = Database::getManager();
11872
        /** @var CLpCategory $item */
11873
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11874
        if ($item) {
11875
            $position = $item->getPosition() - 1;
11876
            $item->setPosition($position);
11877
            $em->persist($item);
11878
            $em->flush();
11879
        }
11880
    }
11881
11882
    /**
11883
     * @param int $id
11884
     *
11885
     * @throws \Doctrine\ORM\ORMException
11886
     * @throws \Doctrine\ORM\OptimisticLockException
11887
     * @throws \Doctrine\ORM\TransactionRequiredException
11888
     */
11889
    public static function moveDownCategory($id)
11890
    {
11891
        $id = (int) $id;
11892
        $em = Database::getManager();
11893
        /** @var CLpCategory $item */
11894
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11895
        if ($item) {
11896
            $position = $item->getPosition() + 1;
11897
            $item->setPosition($position);
11898
            $em->persist($item);
11899
            $em->flush();
11900
        }
11901
    }
11902
11903
    /**
11904
     * @param int $courseId
11905
     *
11906
     * @throws \Doctrine\ORM\Query\QueryException
11907
     *
11908
     * @return int|mixed
11909
     */
11910
    public static function getCountCategories($courseId)
11911
    {
11912
        if (empty($courseId)) {
11913
            return 0;
11914
        }
11915
        $em = Database::getManager();
11916
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
11917
        $query->setParameter('id', $courseId);
11918
11919
        return $query->getSingleScalarResult();
11920
    }
11921
11922
    /**
11923
     * @param int $courseId
11924
     *
11925
     * @return mixed
11926
     */
11927
    public static function getCategories($courseId)
11928
    {
11929
        $em = Database::getManager();
11930
11931
        // Using doctrine extensions
11932
        /** @var SortableRepository $repo */
11933
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
11934
        $items = $repo
11935
            ->getBySortableGroupsQuery(['cId' => $courseId])
11936
            ->getResult();
11937
11938
        return $items;
11939
    }
11940
11941
    /**
11942
     * @param int $id
11943
     *
11944
     * @throws \Doctrine\ORM\ORMException
11945
     * @throws \Doctrine\ORM\OptimisticLockException
11946
     * @throws \Doctrine\ORM\TransactionRequiredException
11947
     *
11948
     * @return CLpCategory
11949
     */
11950
    public static function getCategory($id)
11951
    {
11952
        $id = (int) $id;
11953
        $em = Database::getManager();
11954
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11955
11956
        return $item;
11957
    }
11958
11959
    /**
11960
     * @param int $courseId
11961
     *
11962
     * @return array
11963
     */
11964
    public static function getCategoryByCourse($courseId)
11965
    {
11966
        $em = Database::getManager();
11967
        $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
11968
            ['cId' => $courseId]
11969
        );
11970
11971
        return $items;
11972
    }
11973
11974
    /**
11975
     * @param int $id
11976
     *
11977
     * @throws \Doctrine\ORM\ORMException
11978
     * @throws \Doctrine\ORM\OptimisticLockException
11979
     * @throws \Doctrine\ORM\TransactionRequiredException
11980
     *
11981
     * @return mixed
11982
     */
11983
    public static function deleteCategory($id)
11984
    {
11985
        $em = Database::getManager();
11986
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11987
        if ($item) {
11988
            $courseId = $item->getCId();
11989
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
11990
            $query->setParameter('id', $courseId);
11991
            $query->setParameter('catId', $item->getId());
11992
            $lps = $query->getResult();
11993
11994
            // Setting category = 0.
11995
            if ($lps) {
11996
                foreach ($lps as $lpItem) {
11997
                    $lpItem->setCategoryId(0);
11998
                }
11999
            }
12000
12001
            // Removing category.
12002
            $em->remove($item);
12003
            $em->flush();
12004
12005
            $courseInfo = api_get_course_info_by_id($courseId);
12006
            $sessionId = api_get_session_id();
12007
12008
            // Delete link tool
12009
            /*$tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
12010
            $link = 'lp/lp_controller.php?cid='.$courseInfo['real_id'].'&sid='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
12011
            // Delete tools
12012
            $sql = "DELETE FROM $tbl_tool
12013
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
12014
            Database::query($sql);*/
12015
12016
            return true;
12017
        }
12018
12019
        return false;
12020
    }
12021
12022
    /**
12023
     * @param int  $courseId
12024
     * @param bool $addSelectOption
12025
     *
12026
     * @return mixed
12027
     */
12028
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
12029
    {
12030
        $items = self::getCategoryByCourse($courseId);
12031
        $cats = [];
12032
        if ($addSelectOption) {
12033
            $cats = [get_lang('Select a category')];
12034
        }
12035
12036
        if (!empty($items)) {
12037
            foreach ($items as $cat) {
12038
                $cats[$cat->getId()] = $cat->getName();
12039
            }
12040
        }
12041
12042
        return $cats;
12043
    }
12044
12045
    /**
12046
     * @param string $courseCode
12047
     * @param int    $lpId
12048
     * @param int    $user_id
12049
     *
12050
     * @return learnpath
12051
     */
12052
    public static function getLpFromSession($courseCode, $lpId, $user_id)
12053
    {
12054
        $debug = 0;
12055
        $learnPath = null;
12056
        $lpObject = Session::read('lpobject');
12057
        if ($lpObject !== null) {
12058
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
12059
            if ($debug) {
12060
                error_log('getLpFromSession: unserialize');
12061
                error_log('------getLpFromSession------');
12062
                error_log('------unserialize------');
12063
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12064
                error_log("api_get_sessionid: ".api_get_session_id());
12065
            }
12066
        }
12067
12068
        if (!is_object($learnPath)) {
12069
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
12070
            if ($debug) {
12071
                error_log('------getLpFromSession------');
12072
                error_log('getLpFromSession: create new learnpath');
12073
                error_log("create new LP with $courseCode - $lpId - $user_id");
12074
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12075
                error_log("api_get_sessionid: ".api_get_session_id());
12076
            }
12077
        }
12078
12079
        return $learnPath;
12080
    }
12081
12082
    /**
12083
     * @param int $itemId
12084
     *
12085
     * @return learnpathItem|false
12086
     */
12087
    public function getItem($itemId)
12088
    {
12089
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
12090
            return $this->items[$itemId];
12091
        }
12092
12093
        return false;
12094
    }
12095
12096
    /**
12097
     * @return int
12098
     */
12099
    public function getCurrentAttempt()
12100
    {
12101
        $attempt = $this->getItem($this->get_current_item_id());
12102
        if ($attempt) {
12103
            $attemptId = $attempt->get_attempt_id();
12104
12105
            return $attemptId;
12106
        }
12107
12108
        return 0;
12109
    }
12110
12111
    /**
12112
     * @return int
12113
     */
12114
    public function getCategoryId()
12115
    {
12116
        return (int) $this->categoryId;
12117
    }
12118
12119
    /**
12120
     * @param int $categoryId
12121
     *
12122
     * @return bool
12123
     */
12124
    public function setCategoryId($categoryId)
12125
    {
12126
        $this->categoryId = (int) $categoryId;
12127
        $table = Database::get_course_table(TABLE_LP_MAIN);
12128
        $lp_id = $this->get_id();
12129
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
12130
                WHERE iid = $lp_id";
12131
        Database::query($sql);
12132
12133
        return true;
12134
    }
12135
12136
    /**
12137
     * Get whether this is a learning path with the possibility to subscribe
12138
     * users or not.
12139
     *
12140
     * @return int
12141
     */
12142
    public function getSubscribeUsers()
12143
    {
12144
        return $this->subscribeUsers;
12145
    }
12146
12147
    /**
12148
     * Set whether this is a learning path with the possibility to subscribe
12149
     * users or not.
12150
     *
12151
     * @param int $value (0 = false, 1 = true)
12152
     *
12153
     * @return bool
12154
     */
12155
    public function setSubscribeUsers($value)
12156
    {
12157
        $this->subscribeUsers = (int) $value;
12158
        $table = Database::get_course_table(TABLE_LP_MAIN);
12159
        $lp_id = $this->get_id();
12160
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
12161
                WHERE iid = $lp_id";
12162
        Database::query($sql);
12163
12164
        return true;
12165
    }
12166
12167
    /**
12168
     * Calculate the count of stars for a user in this LP
12169
     * This calculation is based on the following rules:
12170
     * - the student gets one star when he gets to 50% of the learning path
12171
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
12172
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
12173
     * - the student gets the final star when the score for the *last* test is >= 80%.
12174
     *
12175
     * @param int $sessionId Optional. The session ID
12176
     *
12177
     * @return int The count of stars
12178
     */
12179
    public function getCalculateStars($sessionId = 0)
12180
    {
12181
        $stars = 0;
12182
        $progress = self::getProgress(
12183
            $this->lp_id,
12184
            $this->user_id,
12185
            $this->course_int_id,
12186
            $sessionId
12187
        );
12188
12189
        if ($progress >= 50) {
12190
            $stars++;
12191
        }
12192
12193
        // Calculate stars chapters evaluation
12194
        $exercisesItems = $this->getExercisesItems();
12195
12196
        if (!empty($exercisesItems)) {
12197
            $totalResult = 0;
12198
12199
            foreach ($exercisesItems as $exerciseItem) {
12200
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12201
                    $this->user_id,
12202
                    $exerciseItem->path,
12203
                    $this->course_int_id,
12204
                    $sessionId,
12205
                    $this->lp_id,
12206
                    $exerciseItem->db_id
12207
                );
12208
12209
                $exerciseResultInfo = end($exerciseResultInfo);
12210
12211
                if (!$exerciseResultInfo) {
12212
                    continue;
12213
                }
12214
12215
                if (!empty($exerciseResultInfo['max_score'])) {
12216
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
12217
                } else {
12218
                    $exerciseResult = 0;
12219
                }
12220
                $totalResult += $exerciseResult;
12221
            }
12222
12223
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
12224
12225
            if ($totalExerciseAverage >= 50) {
12226
                $stars++;
12227
            }
12228
12229
            if ($totalExerciseAverage >= 80) {
12230
                $stars++;
12231
            }
12232
        }
12233
12234
        // Calculate star for final evaluation
12235
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12236
12237
        if (!empty($finalEvaluationItem)) {
12238
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12239
                $this->user_id,
12240
                $finalEvaluationItem->path,
12241
                $this->course_int_id,
12242
                $sessionId,
12243
                $this->lp_id,
12244
                $finalEvaluationItem->db_id
12245
            );
12246
12247
            $evaluationResultInfo = end($evaluationResultInfo);
12248
12249
            if ($evaluationResultInfo) {
12250
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
12251
12252
                if ($evaluationResult >= 80) {
12253
                    $stars++;
12254
                }
12255
            }
12256
        }
12257
12258
        return $stars;
12259
    }
12260
12261
    /**
12262
     * Get the items of exercise type.
12263
     *
12264
     * @return array The items. Otherwise return false
12265
     */
12266
    public function getExercisesItems()
12267
    {
12268
        $exercises = [];
12269
        foreach ($this->items as $item) {
12270
            if ($item->type != 'quiz') {
12271
                continue;
12272
            }
12273
            $exercises[] = $item;
12274
        }
12275
12276
        array_pop($exercises);
12277
12278
        return $exercises;
12279
    }
12280
12281
    /**
12282
     * Get the item of exercise type (evaluation type).
12283
     *
12284
     * @return array The final evaluation. Otherwise return false
12285
     */
12286
    public function getFinalEvaluationItem()
12287
    {
12288
        $exercises = [];
12289
        foreach ($this->items as $item) {
12290
            if ($item->type != 'quiz') {
12291
                continue;
12292
            }
12293
12294
            $exercises[] = $item;
12295
        }
12296
12297
        return array_pop($exercises);
12298
    }
12299
12300
    /**
12301
     * Calculate the total points achieved for the current user in this learning path.
12302
     *
12303
     * @param int $sessionId Optional. The session Id
12304
     *
12305
     * @return int
12306
     */
12307
    public function getCalculateScore($sessionId = 0)
12308
    {
12309
        // Calculate stars chapters evaluation
12310
        $exercisesItems = $this->getExercisesItems();
12311
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12312
        $totalExercisesResult = 0;
12313
        $totalEvaluationResult = 0;
12314
12315
        if ($exercisesItems !== false) {
12316
            foreach ($exercisesItems as $exerciseItem) {
12317
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12318
                    $this->user_id,
12319
                    $exerciseItem->path,
12320
                    $this->course_int_id,
12321
                    $sessionId,
12322
                    $this->lp_id,
12323
                    $exerciseItem->db_id
12324
                );
12325
12326
                $exerciseResultInfo = end($exerciseResultInfo);
12327
12328
                if (!$exerciseResultInfo) {
12329
                    continue;
12330
                }
12331
12332
                $totalExercisesResult += $exerciseResultInfo['score'];
12333
            }
12334
        }
12335
12336
        if (!empty($finalEvaluationItem)) {
12337
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12338
                $this->user_id,
12339
                $finalEvaluationItem->path,
12340
                $this->course_int_id,
12341
                $sessionId,
12342
                $this->lp_id,
12343
                $finalEvaluationItem->db_id
12344
            );
12345
12346
            $evaluationResultInfo = end($evaluationResultInfo);
12347
12348
            if ($evaluationResultInfo) {
12349
                $totalEvaluationResult += $evaluationResultInfo['score'];
12350
            }
12351
        }
12352
12353
        return $totalExercisesResult + $totalEvaluationResult;
12354
    }
12355
12356
    /**
12357
     * Check if URL is not allowed to be show in a iframe.
12358
     *
12359
     * @param string $src
12360
     *
12361
     * @return string
12362
     */
12363
    public function fixBlockedLinks($src)
12364
    {
12365
        $urlInfo = parse_url($src);
12366
12367
        $platformProtocol = 'https';
12368
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
12369
            $platformProtocol = 'http';
12370
        }
12371
12372
        $protocolFixApplied = false;
12373
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
12374
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
12375
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
12376
12377
        if ($platformProtocol != $scheme) {
12378
            Session::write('x_frame_source', $src);
12379
            $src = 'blank.php?error=x_frames_options';
12380
            $protocolFixApplied = true;
12381
        }
12382
12383
        if ($protocolFixApplied == false) {
12384
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
12385
                // Check X-Frame-Options
12386
                $ch = curl_init();
12387
                $options = [
12388
                    CURLOPT_URL => $src,
12389
                    CURLOPT_RETURNTRANSFER => true,
12390
                    CURLOPT_HEADER => true,
12391
                    CURLOPT_FOLLOWLOCATION => true,
12392
                    CURLOPT_ENCODING => "",
12393
                    CURLOPT_AUTOREFERER => true,
12394
                    CURLOPT_CONNECTTIMEOUT => 120,
12395
                    CURLOPT_TIMEOUT => 120,
12396
                    CURLOPT_MAXREDIRS => 10,
12397
                ];
12398
12399
                $proxySettings = api_get_configuration_value('proxy_settings');
12400
                if (!empty($proxySettings) &&
12401
                    isset($proxySettings['curl_setopt_array'])
12402
                ) {
12403
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
12404
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
12405
                }
12406
12407
                curl_setopt_array($ch, $options);
12408
                $response = curl_exec($ch);
12409
                $httpCode = curl_getinfo($ch);
12410
                $headers = substr($response, 0, $httpCode['header_size']);
12411
12412
                $error = false;
12413
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
12414
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
12415
                ) {
12416
                    $error = true;
12417
                }
12418
12419
                if ($error) {
12420
                    Session::write('x_frame_source', $src);
12421
                    $src = 'blank.php?error=x_frames_options';
12422
                }
12423
            }
12424
        }
12425
12426
        return $src;
12427
    }
12428
12429
    /**
12430
     * Check if this LP has a created forum in the basis course.
12431
     *
12432
     * @return bool
12433
     */
12434
    public function lpHasForum()
12435
    {
12436
        $forumTable = Database::get_course_table(TABLE_FORUM);
12437
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12438
12439
        $fakeFrom = "
12440
            $forumTable f
12441
            INNER JOIN $itemProperty ip
12442
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
12443
        ";
12444
12445
        $resultData = Database::select(
12446
            'COUNT(f.iid) AS qty',
12447
            $fakeFrom,
12448
            [
12449
                'where' => [
12450
                    'ip.visibility != ? AND ' => 2,
12451
                    'ip.tool = ? AND ' => TOOL_FORUM,
12452
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12453
                    'f.lp_id = ?' => intval($this->lp_id),
12454
                ],
12455
            ],
12456
            'first'
12457
        );
12458
12459
        return $resultData['qty'] > 0;
12460
    }
12461
12462
    /**
12463
     * Get the forum for this learning path.
12464
     *
12465
     * @param int $sessionId
12466
     *
12467
     * @return array
12468
     */
12469
    public function getForum($sessionId = 0)
12470
    {
12471
        $repo = Container::getForumRepository();
12472
12473
        $course = api_get_course_entity();
12474
        $session = api_get_session_entity($sessionId);
12475
        $qb = $repo->getResourcesByCourse($course, $session);
12476
12477
        return $qb->getQuery()->getResult();
12478
    }
12479
12480
    /**
12481
     * Create a forum for this learning path.
12482
     *
12483
     * @param int $forumCategoryId
12484
     *
12485
     * @return int The forum ID if was created. Otherwise return false
12486
     */
12487
    public function createForum($forumCategoryId)
12488
    {
12489
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
12490
12491
        $forumId = store_forum(
12492
            [
12493
                'lp_id' => $this->lp_id,
12494
                'forum_title' => $this->name,
12495
                'forum_comment' => null,
12496
                'forum_category' => (int) $forumCategoryId,
12497
                'students_can_edit_group' => ['students_can_edit' => 0],
12498
                'allow_new_threads_group' => ['allow_new_threads' => 0],
12499
                'default_view_type_group' => ['default_view_type' => 'flat'],
12500
                'group_forum' => 0,
12501
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
12502
            ],
12503
            [],
12504
            true
12505
        );
12506
12507
        return $forumId;
12508
    }
12509
12510
    /**
12511
     * Get the LP Final Item form.
12512
     *
12513
     * @throws Exception
12514
     * @throws HTML_QuickForm_Error
12515
     *
12516
     * @return string
12517
     */
12518
    public function getFinalItemForm()
12519
    {
12520
        $finalItem = $this->getFinalItem();
12521
        $title = '';
12522
12523
        if ($finalItem) {
12524
            $title = $finalItem->get_title();
12525
            $buttonText = get_lang('Save');
12526
            $content = $this->getSavedFinalItem();
12527
        } else {
12528
            $buttonText = get_lang('Add this document to the course');
12529
            $content = $this->getFinalItemTemplate();
12530
        }
12531
12532
        $courseInfo = api_get_course_info();
12533
        $result = $this->generate_lp_folder($courseInfo);
12534
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
12535
        $relative_prefix = '../../';
12536
12537
        $editorConfig = [
12538
            'ToolbarSet' => 'LearningPathDocuments',
12539
            'Width' => '100%',
12540
            'Height' => '500',
12541
            'FullPage' => true,
12542
            'CreateDocumentDir' => $relative_prefix,
12543
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
12544
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
12545
        ];
12546
12547
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
12548
            'type' => 'document',
12549
            'lp_id' => $this->lp_id,
12550
        ]);
12551
12552
        $form = new FormValidator('final_item', 'POST', $url);
12553
        $form->addText('title', get_lang('Title'));
12554
        $form->addButtonSave($buttonText);
12555
        $form->addHtml(
12556
            Display::return_message(
12557
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
12558
                'normal',
12559
                false
12560
            )
12561
        );
12562
12563
        $renderer = $form->defaultRenderer();
12564
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
12565
12566
        $form->addHtmlEditor(
12567
            'content_lp_certificate',
12568
            null,
12569
            true,
12570
            false,
12571
            $editorConfig,
12572
            true
12573
        );
12574
        $form->addHidden('action', 'add_final_item');
12575
        $form->addHidden('path', Session::read('pathItem'));
12576
        $form->addHidden('previous', $this->get_last());
12577
        $form->setDefaults(
12578
            ['title' => $title, 'content_lp_certificate' => $content]
12579
        );
12580
12581
        if ($form->validate()) {
12582
            $values = $form->exportValues();
12583
            $lastItemId = $this->getLastInFirstLevel();
12584
12585
            if (!$finalItem) {
12586
                $documentId = $this->create_document(
12587
                    $this->course_info,
12588
                    $values['content_lp_certificate'],
12589
                    $values['title']
12590
                );
12591
                $this->add_item(
12592
                    0,
12593
                    $lastItemId,
12594
                    'final_item',
12595
                    $documentId,
12596
                    $values['title'],
12597
                    ''
12598
                );
12599
12600
                Display::addFlash(
12601
                    Display::return_message(get_lang('Added'))
12602
                );
12603
            } else {
12604
                $this->edit_document($this->course_info);
12605
            }
12606
        }
12607
12608
        return $form->returnForm();
12609
    }
12610
12611
    /**
12612
     * Check if the current lp item is first, both, last or none from lp list.
12613
     *
12614
     * @param int $currentItemId
12615
     *
12616
     * @return string
12617
     */
12618
    public function isFirstOrLastItem($currentItemId)
12619
    {
12620
        $lpItemId = [];
12621
        $typeListNotToVerify = self::getChapterTypes();
12622
12623
        // Using get_toc() function instead $this->items because returns the correct order of the items
12624
        foreach ($this->get_toc() as $item) {
12625
            if (!in_array($item['type'], $typeListNotToVerify)) {
12626
                $lpItemId[] = $item['id'];
12627
            }
12628
        }
12629
12630
        $lastLpItemIndex = count($lpItemId) - 1;
12631
        $position = array_search($currentItemId, $lpItemId);
12632
12633
        switch ($position) {
12634
            case 0:
12635
                if (!$lastLpItemIndex) {
12636
                    $answer = 'both';
12637
                    break;
12638
                }
12639
12640
                $answer = 'first';
12641
                break;
12642
            case $lastLpItemIndex:
12643
                $answer = 'last';
12644
                break;
12645
            default:
12646
                $answer = 'none';
12647
        }
12648
12649
        return $answer;
12650
    }
12651
12652
    /**
12653
     * Get whether this is a learning path with the accumulated SCORM time or not.
12654
     *
12655
     * @return int
12656
     */
12657
    public function getAccumulateScormTime()
12658
    {
12659
        return $this->accumulateScormTime;
12660
    }
12661
12662
    /**
12663
     * Set whether this is a learning path with the accumulated SCORM time or not.
12664
     *
12665
     * @param int $value (0 = false, 1 = true)
12666
     *
12667
     * @return bool Always returns true
12668
     */
12669
    public function setAccumulateScormTime($value)
12670
    {
12671
        $this->accumulateScormTime = (int) $value;
12672
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
12673
        $lp_id = $this->get_id();
12674
        $sql = "UPDATE $lp_table
12675
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
12676
                WHERE iid = $lp_id";
12677
        Database::query($sql);
12678
12679
        return true;
12680
    }
12681
12682
    /**
12683
     * Returns an HTML-formatted link to a resource, to incorporate directly into
12684
     * the new learning path tool.
12685
     *
12686
     * The function is a big switch on tool type.
12687
     * In each case, we query the corresponding table for information and build the link
12688
     * with that information.
12689
     *
12690
     * @author Yannick Warnier <[email protected]> - rebranding based on
12691
     * previous work (display_addedresource_link_in_learnpath())
12692
     *
12693
     * @param int $course_id      Course code
12694
     * @param int $learningPathId The learning path ID (in lp table)
12695
     * @param int $id_in_path     the unique index in the items table
12696
     * @param int $lpViewId
12697
     *
12698
     * @return string
12699
     */
12700
    public static function rl_get_resource_link_for_learnpath(
12701
        $course_id,
12702
        $learningPathId,
12703
        $id_in_path,
12704
        $lpViewId
12705
    ) {
12706
        $session_id = api_get_session_id();
12707
        $course_info = api_get_course_info_by_id($course_id);
12708
12709
        $learningPathId = (int) $learningPathId;
12710
        $id_in_path = (int) $id_in_path;
12711
        $lpViewId = (int) $lpViewId;
12712
12713
        $em = Database::getManager();
12714
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
12715
12716
        /** @var CLpItem $rowItem */
12717
        $rowItem = $lpItemRepo->findOneBy([
12718
            'cId' => $course_id,
12719
            'lpId' => $learningPathId,
12720
            'iid' => $id_in_path,
12721
        ]);
12722
12723
        if (!$rowItem) {
12724
            // Try one more time with "id"
12725
            /** @var CLpItem $rowItem */
12726
            $rowItem = $lpItemRepo->findOneBy([
12727
                'cId' => $course_id,
12728
                'lpId' => $learningPathId,
12729
                'id' => $id_in_path,
12730
            ]);
12731
12732
            if (!$rowItem) {
12733
                return -1;
12734
            }
12735
        }
12736
12737
        $type = $rowItem->getItemType();
12738
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
12739
        $main_dir_path = api_get_path(WEB_CODE_PATH);
12740
        //$main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
12741
        $link = '';
12742
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
12743
12744
        switch ($type) {
12745
            case 'dir':
12746
                return $main_dir_path.'lp/blank.php';
12747
            case TOOL_CALENDAR_EVENT:
12748
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
12749
            case TOOL_ANNOUNCEMENT:
12750
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
12751
            case TOOL_LINK:
12752
                $linkInfo = Link::getLinkInfo($id);
12753
                if (isset($linkInfo['url'])) {
12754
                    return $linkInfo['url'];
12755
                }
12756
12757
                return '';
12758
            case TOOL_QUIZ:
12759
                if (empty($id)) {
12760
                    return '';
12761
                }
12762
12763
                // Get the lp_item_view with the highest view_count.
12764
                $learnpathItemViewResult = $em
12765
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
12766
                    ->findBy(
12767
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
12768
                        ['viewCount' => 'DESC'],
12769
                        1
12770
                    );
12771
                /** @var CLpItemView $learnpathItemViewData */
12772
                $learnpathItemViewData = current($learnpathItemViewResult);
12773
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
12774
12775
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
12776
                    .http_build_query([
12777
                        'lp_init' => 1,
12778
                        'learnpath_item_view_id' => $learnpathItemViewId,
12779
                        'learnpath_id' => $learningPathId,
12780
                        'learnpath_item_id' => $id_in_path,
12781
                        'exerciseId' => $id,
12782
                    ]);
12783
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
12784
                /*$TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
12785
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
12786
                $myrow = Database::fetch_array($result);
12787
                $path = $myrow['path'];
12788
12789
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
12790
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
12791
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;*/
12792
            case TOOL_FORUM:
12793
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
12794
            case TOOL_THREAD:
12795
                // forum post
12796
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
12797
                if (empty($id)) {
12798
                    return '';
12799
                }
12800
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
12801
                $result = Database::query($sql);
12802
                $myrow = Database::fetch_array($result);
12803
12804
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
12805
                    .$extraParams;
12806
            case TOOL_POST:
12807
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12808
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
12809
                $myrow = Database::fetch_array($result);
12810
12811
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
12812
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
12813
            case TOOL_READOUT_TEXT:
12814
                return api_get_path(WEB_CODE_PATH).
12815
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
12816
            case TOOL_DOCUMENT:
12817
                $repo = Container::getDocumentRepository();
12818
                $document = $repo->find($rowItem->getPath());
12819
                $file = $repo->getResourceFileUrl($document, [], UrlGeneratorInterface::ABSOLUTE_URL);
12820
12821
                return $file;
12822
12823
                $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...
12824
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v'];
12825
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
12826
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
12827
12828
                $openmethod = 2;
12829
                $officedoc = false;
12830
                Session::write('openmethod', $openmethod);
12831
                Session::write('officedoc', $officedoc);
12832
12833
                if ($showDirectUrl) {
12834
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
12835
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
12836
                        if (Link::isPdfLink($file)) {
12837
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
12838
12839
                            return $pdfUrl;
12840
                        }
12841
                    }
12842
12843
                    return $file;
12844
                }
12845
12846
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
12847
            case TOOL_LP_FINAL_ITEM:
12848
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
12849
                    .$extraParams;
12850
            case 'assignments':
12851
                return $main_dir_path.'work/work.php?'.$extraParams;
12852
            case TOOL_DROPBOX:
12853
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
12854
            case 'introduction_text': //DEPRECATED
12855
                return '';
12856
            case TOOL_COURSE_DESCRIPTION:
12857
                return $main_dir_path.'course_description?'.$extraParams;
12858
            case TOOL_GROUP:
12859
                return $main_dir_path.'group/group.php?'.$extraParams;
12860
            case TOOL_USER:
12861
                return $main_dir_path.'user/user.php?'.$extraParams;
12862
            case TOOL_STUDENTPUBLICATION:
12863
                if (!empty($rowItem->getPath())) {
12864
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
12865
                }
12866
12867
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
12868
        }
12869
12870
        return $link;
12871
    }
12872
12873
    /**
12874
     * Gets the name of a resource (generally used in learnpath when no name is provided).
12875
     *
12876
     * @author Yannick Warnier <[email protected]>
12877
     *
12878
     * @param string $course_code    Course code
12879
     * @param int    $learningPathId
12880
     * @param int    $id_in_path     The resource ID
12881
     *
12882
     * @return string
12883
     */
12884
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
12885
    {
12886
        $_course = api_get_course_info($course_code);
12887
        if (empty($_course)) {
12888
            return '';
12889
        }
12890
        $course_id = $_course['real_id'];
12891
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12892
        $learningPathId = (int) $learningPathId;
12893
        $id_in_path = (int) $id_in_path;
12894
12895
        $sql = "SELECT item_type, title, ref
12896
                FROM $tbl_lp_item
12897
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
12898
        $res_item = Database::query($sql);
12899
12900
        if (Database::num_rows($res_item) < 1) {
12901
            return '';
12902
        }
12903
        $row_item = Database::fetch_array($res_item);
12904
        $type = strtolower($row_item['item_type']);
12905
        $id = $row_item['ref'];
12906
        $output = '';
12907
12908
        switch ($type) {
12909
            case TOOL_CALENDAR_EVENT:
12910
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
12911
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
12912
                $myrow = Database::fetch_array($result);
12913
                $output = $myrow['title'];
12914
                break;
12915
            case TOOL_ANNOUNCEMENT:
12916
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
12917
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
12918
                $myrow = Database::fetch_array($result);
12919
                $output = $myrow['title'];
12920
                break;
12921
            case TOOL_LINK:
12922
                // Doesn't take $target into account.
12923
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
12924
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
12925
                $myrow = Database::fetch_array($result);
12926
                $output = $myrow['title'];
12927
                break;
12928
            case TOOL_QUIZ:
12929
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
12930
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
12931
                $myrow = Database::fetch_array($result);
12932
                $output = $myrow['title'];
12933
                break;
12934
            case TOOL_FORUM:
12935
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
12936
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
12937
                $myrow = Database::fetch_array($result);
12938
                $output = $myrow['forum_name'];
12939
                break;
12940
            case TOOL_THREAD:
12941
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12942
                // Grabbing the title of the post.
12943
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
12944
                $result_title = Database::query($sql_title);
12945
                $myrow_title = Database::fetch_array($result_title);
12946
                $output = $myrow_title['post_title'];
12947
                break;
12948
            case TOOL_POST:
12949
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12950
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
12951
                $result = Database::query($sql);
12952
                $post = Database::fetch_array($result);
12953
                $output = $post['post_title'];
12954
                break;
12955
            case 'dir':
12956
            case TOOL_DOCUMENT:
12957
                $title = $row_item['title'];
12958
                $output = '-';
12959
                if (!empty($title)) {
12960
                    $output = $title;
12961
                }
12962
                break;
12963
            case 'hotpotatoes':
12964
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
12965
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
12966
                $myrow = Database::fetch_array($result);
12967
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
12968
                $last = count($pathname) - 1; // Making a correct name for the link.
12969
                $filename = $pathname[$last]; // Making a correct name for the link.
12970
                $myrow['path'] = rawurlencode($myrow['path']);
12971
                $output = $filename;
12972
                break;
12973
        }
12974
12975
        return stripslashes($output);
12976
    }
12977
12978
    /**
12979
     * Get the parent names for the current item.
12980
     *
12981
     * @param int $newItemId Optional. The item ID
12982
     *
12983
     * @return array
12984
     */
12985
    public function getCurrentItemParentNames($newItemId = 0)
12986
    {
12987
        $newItemId = $newItemId ?: $this->get_current_item_id();
12988
        $return = [];
12989
        $item = $this->getItem($newItemId);
12990
        $parent = $this->getItem($item->get_parent());
12991
12992
        while ($parent) {
12993
            $return[] = $parent->get_title();
12994
            $parent = $this->getItem($parent->get_parent());
12995
        }
12996
12997
        return array_reverse($return);
12998
    }
12999
13000
    /**
13001
     * Reads and process "lp_subscription_settings" setting.
13002
     *
13003
     * @return array
13004
     */
13005
    public static function getSubscriptionSettings()
13006
    {
13007
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
13008
        if (empty($subscriptionSettings)) {
13009
            // By default allow both settings
13010
            $subscriptionSettings = [
13011
                'allow_add_users_to_lp' => true,
13012
                'allow_add_users_to_lp_category' => true,
13013
            ];
13014
        } else {
13015
            $subscriptionSettings = $subscriptionSettings['options'];
13016
        }
13017
13018
        return $subscriptionSettings;
13019
    }
13020
13021
    /**
13022
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
13023
     */
13024
    public function exportToCourseBuildFormat()
13025
    {
13026
        if (!api_is_allowed_to_edit()) {
13027
            return false;
13028
        }
13029
13030
        $courseBuilder = new CourseBuilder();
13031
        $itemList = [];
13032
        /** @var learnpathItem $item */
13033
        foreach ($this->items as $item) {
13034
            $itemList[$item->get_type()][] = $item->get_path();
13035
        }
13036
13037
        if (empty($itemList)) {
13038
            return false;
13039
        }
13040
13041
        if (isset($itemList['document'])) {
13042
            // Get parents
13043
            foreach ($itemList['document'] as $documentId) {
13044
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
13045
                if (!empty($documentInfo['parents'])) {
13046
                    foreach ($documentInfo['parents'] as $parentInfo) {
13047
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
13048
                            continue;
13049
                        }
13050
                        $itemList['document'][] = $parentInfo['iid'];
13051
                    }
13052
                }
13053
            }
13054
13055
            $courseInfo = api_get_course_info();
13056
            foreach ($itemList['document'] as $documentId) {
13057
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
13058
                $items = DocumentManager::get_resources_from_source_html(
13059
                    $documentInfo['absolute_path'],
13060
                    true,
13061
                    TOOL_DOCUMENT
13062
                );
13063
13064
                if (!empty($items)) {
13065
                    foreach ($items as $item) {
13066
                        // Get information about source url
13067
                        $url = $item[0]; // url
13068
                        $scope = $item[1]; // scope (local, remote)
13069
                        $type = $item[2]; // type (rel, abs, url)
13070
13071
                        $origParseUrl = parse_url($url);
13072
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
13073
13074
                        if ($scope == 'local') {
13075
                            if ($type == 'abs' || $type == 'rel') {
13076
                                $documentFile = strstr($realOrigPath, 'document');
13077
                                if (strpos($realOrigPath, $documentFile) !== false) {
13078
                                    $documentFile = str_replace('document', '', $documentFile);
13079
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
13080
                                    // Document found! Add it to the list
13081
                                    if ($itemDocumentId) {
13082
                                        $itemList['document'][] = $itemDocumentId;
13083
                                    }
13084
                                }
13085
                            }
13086
                        }
13087
                    }
13088
                }
13089
            }
13090
13091
            $courseBuilder->build_documents(
13092
                api_get_session_id(),
13093
                $this->get_course_int_id(),
13094
                true,
13095
                $itemList['document']
13096
            );
13097
        }
13098
13099
        if (isset($itemList['quiz'])) {
13100
            $courseBuilder->build_quizzes(
13101
                api_get_session_id(),
13102
                $this->get_course_int_id(),
13103
                true,
13104
                $itemList['quiz']
13105
            );
13106
        }
13107
13108
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
13109
13110
        /*if (!empty($itemList['thread'])) {
13111
            $postList = [];
13112
            foreach ($itemList['thread'] as $postId) {
13113
                $post = get_post_information($postId);
13114
                if ($post) {
13115
                    if (!isset($itemList['forum'])) {
13116
                        $itemList['forum'] = [];
13117
                    }
13118
                    $itemList['forum'][] = $post['forum_id'];
13119
                    $postList[] = $postId;
13120
                }
13121
            }
13122
13123
            if (!empty($postList)) {
13124
                $courseBuilder->build_forum_posts(
13125
                    $this->get_course_int_id(),
13126
                    null,
13127
                    null,
13128
                    $postList
13129
                );
13130
            }
13131
        }*/
13132
13133
        if (!empty($itemList['thread'])) {
13134
            $threadList = [];
13135
            $em = Database::getManager();
13136
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
13137
            foreach ($itemList['thread'] as $threadId) {
13138
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
13139
                $thread = $repo->find($threadId);
13140
                if ($thread) {
13141
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
13142
                    $threadList[] = $thread->getIid();
13143
                }
13144
            }
13145
13146
            if (!empty($threadList)) {
13147
                $courseBuilder->build_forum_topics(
13148
                    api_get_session_id(),
13149
                    $this->get_course_int_id(),
13150
                    null,
13151
                    $threadList
13152
                );
13153
            }
13154
        }
13155
13156
        $forumCategoryList = [];
13157
        if (isset($itemList['forum'])) {
13158
            foreach ($itemList['forum'] as $forumId) {
13159
                $forumInfo = get_forums($forumId);
13160
                $forumCategoryList[] = $forumInfo['forum_category'];
13161
            }
13162
        }
13163
13164
        if (!empty($forumCategoryList)) {
13165
            $courseBuilder->build_forum_category(
13166
                api_get_session_id(),
13167
                $this->get_course_int_id(),
13168
                true,
13169
                $forumCategoryList
13170
            );
13171
        }
13172
13173
        if (!empty($itemList['forum'])) {
13174
            $courseBuilder->build_forums(
13175
                api_get_session_id(),
13176
                $this->get_course_int_id(),
13177
                true,
13178
                $itemList['forum']
13179
            );
13180
        }
13181
13182
        if (isset($itemList['link'])) {
13183
            $courseBuilder->build_links(
13184
                api_get_session_id(),
13185
                $this->get_course_int_id(),
13186
                true,
13187
                $itemList['link']
13188
            );
13189
        }
13190
13191
        if (!empty($itemList['student_publication'])) {
13192
            $courseBuilder->build_works(
13193
                api_get_session_id(),
13194
                $this->get_course_int_id(),
13195
                true,
13196
                $itemList['student_publication']
13197
            );
13198
        }
13199
13200
        $courseBuilder->build_learnpaths(
13201
            api_get_session_id(),
13202
            $this->get_course_int_id(),
13203
            true,
13204
            [$this->get_id()],
13205
            false
13206
        );
13207
13208
        $courseBuilder->restoreDocumentsFromList();
13209
13210
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
13211
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
13212
        $result = DocumentManager::file_send_for_download(
13213
            $zipPath,
13214
            true,
13215
            $this->get_name().'.zip'
13216
        );
13217
13218
        if ($result) {
13219
            api_not_allowed();
13220
        }
13221
13222
        return true;
13223
    }
13224
13225
    /**
13226
     * Get whether this is a learning path with the accumulated work time or not.
13227
     *
13228
     * @return int
13229
     */
13230
    public function getAccumulateWorkTime()
13231
    {
13232
        return (int) $this->accumulateWorkTime;
13233
    }
13234
13235
    /**
13236
     * Get whether this is a learning path with the accumulated work time or not.
13237
     *
13238
     * @return int
13239
     */
13240
    public function getAccumulateWorkTimeTotalCourse()
13241
    {
13242
        $table = Database::get_course_table(TABLE_LP_MAIN);
13243
        $sql = "SELECT SUM(accumulate_work_time) AS total
13244
                FROM $table
13245
                WHERE c_id = ".$this->course_int_id;
13246
        $result = Database::query($sql);
13247
        $row = Database::fetch_array($result);
13248
13249
        return (int) $row['total'];
13250
    }
13251
13252
    /**
13253
     * Set whether this is a learning path with the accumulated work time or not.
13254
     *
13255
     * @param int $value (0 = false, 1 = true)
13256
     *
13257
     * @return bool
13258
     */
13259
    public function setAccumulateWorkTime($value)
13260
    {
13261
        if (!api_get_configuration_value('lp_minimum_time')) {
13262
            return false;
13263
        }
13264
13265
        $this->accumulateWorkTime = (int) $value;
13266
        $table = Database::get_course_table(TABLE_LP_MAIN);
13267
        $lp_id = $this->get_id();
13268
        $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
13269
                WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
13270
        Database::query($sql);
13271
13272
        return true;
13273
    }
13274
13275
    /**
13276
     * @param int $lpId
13277
     * @param int $courseId
13278
     *
13279
     * @return mixed
13280
     */
13281
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
13282
    {
13283
        $lpId = (int) $lpId;
13284
        $courseId = (int) $courseId;
13285
13286
        $table = Database::get_course_table(TABLE_LP_MAIN);
13287
        $sql = "SELECT accumulate_work_time
13288
                FROM $table
13289
                WHERE c_id = $courseId AND id = $lpId";
13290
        $result = Database::query($sql);
13291
        $row = Database::fetch_array($result);
13292
13293
        return $row['accumulate_work_time'];
13294
    }
13295
13296
    /**
13297
     * @param int $courseId
13298
     *
13299
     * @return int
13300
     */
13301
    public static function getAccumulateWorkTimeTotal($courseId)
13302
    {
13303
        $table = Database::get_course_table(TABLE_LP_MAIN);
13304
        $courseId = (int) $courseId;
13305
        $sql = "SELECT SUM(accumulate_work_time) AS total
13306
                FROM $table
13307
                WHERE c_id = $courseId";
13308
        $result = Database::query($sql);
13309
        $row = Database::fetch_array($result);
13310
13311
        return (int) $row['total'];
13312
    }
13313
13314
    /**
13315
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
13316
     * and put the images in.
13317
     *
13318
     * @return array
13319
     */
13320
    public static function getIconSelect()
13321
    {
13322
        $theme = api_get_visual_theme();
13323
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
13324
        $icons = ['' => get_lang('Please select an option')];
13325
13326
        if (is_dir($path)) {
13327
            $finder = new Finder();
13328
            $finder->files()->in($path);
13329
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
13330
            /** @var SplFileInfo $file */
13331
            foreach ($finder as $file) {
13332
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
13333
                    $icons[$file->getFilename()] = $file->getFilename();
13334
                }
13335
            }
13336
        }
13337
13338
        return $icons;
13339
    }
13340
13341
    /**
13342
     * @param int $lpId
13343
     *
13344
     * @return string
13345
     */
13346
    public static function getSelectedIcon($lpId)
13347
    {
13348
        $extraFieldValue = new ExtraFieldValue('lp');
13349
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
13350
        $icon = '';
13351
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
13352
            $icon = $lpIcon['value'];
13353
        }
13354
13355
        return $icon;
13356
    }
13357
13358
    /**
13359
     * @param int $lpId
13360
     *
13361
     * @return string
13362
     */
13363
    public static function getSelectedIconHtml($lpId)
13364
    {
13365
        $icon = self::getSelectedIcon($lpId);
13366
13367
        if (empty($icon)) {
13368
            return '';
13369
        }
13370
13371
        $theme = api_get_visual_theme();
13372
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
13373
13374
        return Display::img($path);
13375
    }
13376
13377
    /**
13378
     * @param string $value
13379
     *
13380
     * @return string
13381
     */
13382
    public function cleanItemTitle($value)
13383
    {
13384
        $value = Security::remove_XSS(strip_tags($value));
13385
13386
        return $value;
13387
    }
13388
13389
    public function setItemTitle(FormValidator $form)
13390
    {
13391
        if (api_get_configuration_value('save_titles_as_html')) {
13392
            $form->addHtmlEditor(
13393
                'title',
13394
                get_lang('Title'),
13395
                true,
13396
                false,
13397
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
13398
            );
13399
        } else {
13400
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
13401
            $form->applyFilter('title', 'trim');
13402
            $form->applyFilter('title', 'html_filter');
13403
        }
13404
    }
13405
13406
    /**
13407
     * @return array
13408
     */
13409
    public function getItemsForForm($addParentCondition = false)
13410
    {
13411
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
13412
        $course_id = api_get_course_int_id();
13413
13414
        $sql = "SELECT * FROM $tbl_lp_item
13415
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
13416
13417
        if ($addParentCondition) {
13418
            $sql .= ' AND parent_item_id = 0 ';
13419
        }
13420
        $sql .= ' ORDER BY display_order ASC';
13421
13422
        $result = Database::query($sql);
13423
        $arrLP = [];
13424
        while ($row = Database::fetch_array($result)) {
13425
            $arrLP[] = [
13426
                'iid' => $row['iid'],
13427
                'id' => $row['iid'],
13428
                'item_type' => $row['item_type'],
13429
                'title' => $this->cleanItemTitle($row['title']),
13430
                'title_raw' => $row['title'],
13431
                'path' => $row['path'],
13432
                'description' => Security::remove_XSS($row['description']),
13433
                'parent_item_id' => $row['parent_item_id'],
13434
                'previous_item_id' => $row['previous_item_id'],
13435
                'next_item_id' => $row['next_item_id'],
13436
                'display_order' => $row['display_order'],
13437
                'max_score' => $row['max_score'],
13438
                'min_score' => $row['min_score'],
13439
                'mastery_score' => $row['mastery_score'],
13440
                'prerequisite' => $row['prerequisite'],
13441
                'max_time_allowed' => $row['max_time_allowed'],
13442
                'prerequisite_min_score' => $row['prerequisite_min_score'],
13443
                'prerequisite_max_score' => $row['prerequisite_max_score'],
13444
            ];
13445
        }
13446
13447
        return $arrLP;
13448
    }
13449
13450
    /**
13451
     * Get the depth level of LP item.
13452
     *
13453
     * @param array $items
13454
     * @param int   $currentItemId
13455
     *
13456
     * @return int
13457
     */
13458
    private static function get_level_for_item($items, $currentItemId)
13459
    {
13460
        $parentItemId = 0;
13461
        if (isset($items[$currentItemId])) {
13462
            $parentItemId = $items[$currentItemId]->parent;
13463
        }
13464
13465
        if ($parentItemId == 0) {
13466
            return 0;
13467
        } else {
13468
            return self::get_level_for_item($items, $parentItemId) + 1;
13469
        }
13470
    }
13471
13472
    /**
13473
     * Generate the link for a learnpath category as course tool.
13474
     *
13475
     * @param int $categoryId
13476
     *
13477
     * @return string
13478
     */
13479
    private static function getCategoryLinkForTool($categoryId)
13480
    {
13481
        $categoryId = (int) $categoryId;
13482
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
13483
            .http_build_query(
13484
                [
13485
                    'action' => 'view_category',
13486
                    'id' => $categoryId,
13487
                ]
13488
            );
13489
13490
        return $link;
13491
    }
13492
13493
    /**
13494
     * Return the scorm item type object with spaces replaced with _
13495
     * The return result is use to build a css classname like scorm_type_$return.
13496
     *
13497
     * @param $in_type
13498
     *
13499
     * @return mixed
13500
     */
13501
    private static function format_scorm_type_item($in_type)
13502
    {
13503
        return str_replace(' ', '_', $in_type);
13504
    }
13505
13506
    /**
13507
     * Check and obtain the lp final item if exist.
13508
     *
13509
     * @return learnpathItem
13510
     */
13511
    private function getFinalItem()
13512
    {
13513
        if (empty($this->items)) {
13514
            return null;
13515
        }
13516
13517
        foreach ($this->items as $item) {
13518
            if ($item->type !== 'final_item') {
13519
                continue;
13520
            }
13521
13522
            return $item;
13523
        }
13524
    }
13525
13526
    /**
13527
     * Get the LP Final Item Template.
13528
     *
13529
     * @return string
13530
     */
13531
    private function getFinalItemTemplate()
13532
    {
13533
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
13534
    }
13535
13536
    /**
13537
     * Get the LP Final Item Url.
13538
     *
13539
     * @return string
13540
     */
13541
    private function getSavedFinalItem()
13542
    {
13543
        $finalItem = $this->getFinalItem();
13544
        $doc = DocumentManager::get_document_data_by_id(
13545
            $finalItem->path,
13546
            $this->cc
13547
        );
13548
        if ($doc && file_exists($doc['absolute_path'])) {
13549
            return file_get_contents($doc['absolute_path']);
13550
        }
13551
13552
        return '';
13553
    }
13554
}
13555