Passed
Push — master ( 748ce8...32285e )
by Julito
07:34
created

learnpath   F

Complexity

Total Complexity 1804

Size/Duplication

Total Lines 13513
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 7332
dl 0
loc 13513
rs 0.8
c 4
b 0
f 0
wmc 1804

219 Methods

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

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

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

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
9198
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9199
                $selectPrevious->addOption(
9200
                    $arrLP[$i]['title'],
9201
                    $arrLP[$i]['id']
9202
                );
9203
9204
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9205
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9206
                } elseif ($action == 'add') {
9207
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9208
                }
9209
            }
9210
        }
9211
9212
        if ($action != 'move') {
9213
            $urlAttributes = ['class' => 'learnpath_item_form'];
9214
9215
            if (is_numeric($extra_info)) {
9216
                $urlAttributes['disabled'] = 'disabled';
9217
            }
9218
9219
            $form->addElement('url', 'url', get_lang('URL'), $urlAttributes);
9220
            $defaults['url'] = $item_url;
9221
            $arrHide = [];
9222
            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...
9223
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
9224
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9225
                }
9226
            }
9227
        }
9228
9229
        if ($action == 'add') {
9230
            $form->addButtonSave(get_lang('Add link to course'), 'submit_button');
9231
        } else {
9232
            $form->addButtonSave(get_lang('Edit the current link'), 'submit_button');
9233
        }
9234
9235
        if ($action == 'move') {
9236
            $form->addHidden('title', $item_title);
9237
            $form->addHidden('description', $item_description);
9238
        }
9239
9240
        if (is_numeric($extra_info)) {
9241
            $form->addHidden('path', $extra_info);
9242
        } elseif (is_array($extra_info)) {
9243
            $form->addHidden('path', $extra_info['path']);
9244
        }
9245
        $form->addHidden('type', TOOL_LINK);
9246
        $form->addHidden('post_time', time());
9247
        $form->setDefaults($defaults);
9248
9249
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
9250
    }
9251
9252
    /**
9253
     * Return HTML form to add/edit a student publication (work).
9254
     *
9255
     * @param string $action
9256
     * @param int    $id         Item ID if already exists
9257
     * @param string $extra_info
9258
     *
9259
     * @throws Exception
9260
     *
9261
     * @return string HTML form
9262
     */
9263
    public function display_student_publication_form(
9264
        $action = 'add',
9265
        $id = 0,
9266
        $extra_info = ''
9267
    ) {
9268
        $course_id = api_get_course_int_id();
9269
        $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
9270
9271
        $item_title = get_lang('Assignments');
9272
        if ($id != 0 && is_array($extra_info)) {
9273
            $item_title = stripslashes($extra_info['title']);
9274
            $item_description = stripslashes($extra_info['description']);
9275
        } elseif (is_numeric($extra_info)) {
9276
            $extra_info = (int) $extra_info;
9277
            $sql = "SELECT title, description
9278
                    FROM $tbl_publication
9279
                    WHERE c_id = $course_id AND id = ".$extra_info;
9280
9281
            $result = Database::query($sql);
9282
            $row = Database::fetch_array($result);
9283
            if ($row) {
9284
                $item_title = $row['title'];
9285
            }
9286
        }
9287
9288
        $parent = 0;
9289
        if ($id != 0 && is_array($extra_info)) {
9290
            $parent = $extra_info['parent_item_id'];
9291
        }
9292
9293
        $arrLP = $this->getItemsForForm();
9294
9295
        $this->tree_array($arrLP);
9296
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9297
        unset($this->arrMenu);
9298
9299
        $form = new FormValidator('frm_student_publication', 'post', '#');
9300
9301
        if ($action == 'add') {
9302
            $form->addHeader(get_lang('Assignments'));
9303
        } elseif ($action == 'move') {
9304
            $form->addHeader(get_lang('Move the current assignment'));
9305
        } else {
9306
            $form->addHeader(get_lang('Edit the current assignment'));
9307
        }
9308
9309
        if ($action != 'move') {
9310
            $this->setItemTitle($form);
9311
        }
9312
9313
        $parentSelect = $form->addSelect(
9314
            'parent',
9315
            get_lang('Parent'),
9316
            ['0' => $this->name],
9317
            [
9318
                'onchange' => 'javascript: load_cbo(this.value);',
9319
                'class' => 'learnpath_item_form',
9320
                'id' => 'idParent',
9321
            ]
9322
        );
9323
9324
        $arrHide = [$id];
9325
        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...
9326
            if ($action != 'add') {
9327
                if (
9328
                    ($arrLP[$i]['item_type'] == 'dir') &&
9329
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9330
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9331
                ) {
9332
                    $parentSelect->addOption(
9333
                        $arrLP[$i]['title'],
9334
                        $arrLP[$i]['id'],
9335
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9336
                    );
9337
9338
                    if ($parent == $arrLP[$i]['id']) {
9339
                        $parentSelect->setSelected($arrLP[$i]['id']);
9340
                    }
9341
                } else {
9342
                    $arrHide[] = $arrLP[$i]['id'];
9343
                }
9344
            } else {
9345
                if ($arrLP[$i]['item_type'] == 'dir') {
9346
                    $parentSelect->addOption(
9347
                        $arrLP[$i]['title'],
9348
                        $arrLP[$i]['id'],
9349
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9350
                    );
9351
9352
                    if ($parent == $arrLP[$i]['id']) {
9353
                        $parentSelect->setSelected($arrLP[$i]['id']);
9354
                    }
9355
                }
9356
            }
9357
        }
9358
9359
        if (is_array($arrLP)) {
9360
            reset($arrLP);
9361
        }
9362
9363
        $previousSelect = $form->addSelect(
9364
            'previous',
9365
            get_lang('Position'),
9366
            ['0' => get_lang('First position')],
9367
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9368
        );
9369
9370
        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...
9371
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9372
                $previousSelect->addOption(
9373
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
9374
                    $arrLP[$i]['id']
9375
                );
9376
9377
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9378
                    $previousSelect->setSelected($arrLP[$i]['id']);
9379
                } elseif ($action == 'add') {
9380
                    $previousSelect->setSelected($arrLP[$i]['id']);
9381
                }
9382
            }
9383
        }
9384
9385
        if ($action == 'add') {
9386
            $form->addButtonCreate(get_lang('Add assignment to course'), 'submit_button');
9387
        } else {
9388
            $form->addButtonCreate(get_lang('Edit the current assignment'), 'submit_button');
9389
        }
9390
9391
        if ($action == 'move') {
9392
            $form->addHidden('title', $item_title);
9393
            $form->addHidden('description', $item_description);
9394
        }
9395
9396
        if (is_numeric($extra_info)) {
9397
            $form->addHidden('path', $extra_info);
9398
        } elseif (is_array($extra_info)) {
9399
            $form->addHidden('path', $extra_info['path']);
9400
        }
9401
9402
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
9403
        $form->addHidden('post_time', time());
9404
        $form->setDefaults(['title' => $item_title]);
9405
9406
        $return = '<div class="sectioncomment">';
9407
        $return .= $form->returnForm();
9408
        $return .= '</div>';
9409
9410
        return $return;
9411
    }
9412
9413
    /**
9414
     * Displays the menu for manipulating a step.
9415
     *
9416
     * @param id     $item_id
9417
     * @param string $item_type
9418
     *
9419
     * @return string
9420
     */
9421
    public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
9422
    {
9423
        $_course = api_get_course_info();
9424
        $course_code = api_get_course_id();
9425
        $item_id = (int) $item_id;
9426
9427
        $return = '<div class="actions">';
9428
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9429
        $sql = "SELECT * FROM $tbl_lp_item
9430
                WHERE iid = ".$item_id;
9431
        $result = Database::query($sql);
9432
        $row = Database::fetch_assoc($result);
9433
9434
        $audio_player = null;
9435
        // We display an audio player if needed.
9436
        if (!empty($row['audio'])) {
9437
            $webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
9438
9439
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
9440
                .'<audio src="'.$webAudioPath.'" controls>'
9441
                .'</div><br>';
9442
        }
9443
9444
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
9445
9446
        if ($item_type != TOOL_LP_FINAL_ITEM) {
9447
            $return .= Display::url(
9448
                Display::return_icon(
9449
                    'edit.png',
9450
                    get_lang('Edit'),
9451
                    [],
9452
                    ICON_SIZE_SMALL
9453
                ),
9454
                $url.'&action=edit_item&path_item='.$row['path']
9455
            );
9456
9457
            $return .= Display::url(
9458
                Display::return_icon(
9459
                    'move.png',
9460
                    get_lang('Move'),
9461
                    [],
9462
                    ICON_SIZE_SMALL
9463
                ),
9464
                $url.'&action=move_item'
9465
            );
9466
        }
9467
9468
        // Commented for now as prerequisites cannot be added to chapters.
9469
        if ($item_type != 'dir') {
9470
            $return .= Display::url(
9471
                Display::return_icon(
9472
                    'accept.png',
9473
                    get_lang('Prerequisites'),
9474
                    [],
9475
                    ICON_SIZE_SMALL
9476
                ),
9477
                $url.'&action=edit_item_prereq'
9478
            );
9479
        }
9480
        $return .= Display::url(
9481
            Display::return_icon(
9482
                'delete.png',
9483
                get_lang('Delete'),
9484
                [],
9485
                ICON_SIZE_SMALL
9486
            ),
9487
            $url.'&action=delete_item'
9488
        );
9489
9490
        if (in_array($item_type, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
9491
            $documentData = DocumentManager::get_document_data_by_id($row['path'], $course_code);
9492
            if (empty($documentData)) {
9493
                // Try with iid
9494
                $table = Database::get_course_table(TABLE_DOCUMENT);
9495
                $sql = "SELECT path FROM $table
9496
                        WHERE
9497
                              c_id = ".api_get_course_int_id()." AND
9498
                              iid = ".$row['path']." AND
9499
                              path NOT LIKE '%_DELETED_%'";
9500
                $result = Database::query($sql);
9501
                $documentData = Database::fetch_array($result);
9502
                if ($documentData) {
9503
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
9504
                }
9505
            }
9506
            if (isset($documentData['absolute_path_from_document'])) {
9507
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
9508
            }
9509
        }
9510
9511
        $return .= '</div>';
9512
9513
        if (!empty($audio_player)) {
9514
            $return .= $audio_player;
9515
        }
9516
9517
        return $return;
9518
    }
9519
9520
    /**
9521
     * Creates the javascript needed for filling up the checkboxes without page reload.
9522
     *
9523
     * @return string
9524
     */
9525
    public function get_js_dropdown_array()
9526
    {
9527
        $course_id = api_get_course_int_id();
9528
        $return = 'var child_name = new Array();'."\n";
9529
        $return .= 'var child_value = new Array();'."\n\n";
9530
        $return .= 'child_name[0] = new Array();'."\n";
9531
        $return .= 'child_value[0] = new Array();'."\n\n";
9532
9533
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9534
        $sql = "SELECT * FROM ".$tbl_lp_item."
9535
                WHERE
9536
                    c_id = $course_id AND
9537
                    lp_id = ".$this->lp_id." AND
9538
                    parent_item_id = 0
9539
                ORDER BY display_order ASC";
9540
        $res_zero = Database::query($sql);
9541
        $i = 0;
9542
9543
        $list = $this->getItemsForForm(true);
9544
9545
        foreach ($list as $row_zero) {
9546
            if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
9547
                if ($row_zero['item_type'] == TOOL_QUIZ) {
9548
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
9549
                }
9550
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
9551
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
9552
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
9553
            }
9554
        }
9555
9556
        $return .= "\n";
9557
        $sql = "SELECT * FROM $tbl_lp_item
9558
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9559
        $res = Database::query($sql);
9560
        while ($row = Database::fetch_array($res)) {
9561
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
9562
                           WHERE
9563
                                c_id = ".$course_id." AND
9564
                                parent_item_id = ".$row['iid']."
9565
                           ORDER BY display_order ASC";
9566
            $res_parent = Database::query($sql_parent);
9567
            $i = 0;
9568
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
9569
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
9570
9571
            while ($row_parent = Database::fetch_array($res_parent)) {
9572
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
9573
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
9574
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
9575
            }
9576
            $return .= "\n";
9577
        }
9578
9579
        $return .= "
9580
            function load_cbo(id) {
9581
                if (!id) {
9582
                    return false;
9583
                }
9584
9585
                var cbo = document.getElementById('previous');
9586
                for(var i = cbo.length - 1; i > 0; i--) {
9587
                    cbo.options[i] = null;
9588
                }
9589
9590
                var k=0;
9591
                for(var i = 1; i <= child_name[id].length; i++){
9592
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
9593
                    option.style.paddingLeft = '40px';
9594
                    cbo.options[i] = option;
9595
                    k = i;
9596
                }
9597
9598
                cbo.options[k].selected = true;
9599
                $('#previous').selectpicker('refresh');
9600
            }";
9601
9602
        return $return;
9603
    }
9604
9605
    /**
9606
     * Display the form to allow moving an item.
9607
     *
9608
     * @param int $item_id Item ID
9609
     *
9610
     * @throws Exception
9611
     * @throws HTML_QuickForm_Error
9612
     *
9613
     * @return string HTML form
9614
     */
9615
    public function display_move_item($item_id)
9616
    {
9617
        $return = '';
9618
        if (is_numeric($item_id)) {
9619
            $item_id = (int) $item_id;
9620
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9621
9622
            $sql = "SELECT * FROM $tbl_lp_item
9623
                    WHERE iid = $item_id";
9624
            $res = Database::query($sql);
9625
            $row = Database::fetch_array($res);
9626
9627
            switch ($row['item_type']) {
9628
                case 'dir':
9629
                case 'asset':
9630
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9631
                    $return .= $this->display_item_form(
9632
                        $row['item_type'],
9633
                        get_lang('Move the current section'),
9634
                        'move',
9635
                        $item_id,
9636
                        $row
9637
                    );
9638
                    break;
9639
                case TOOL_DOCUMENT:
9640
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9641
                    $return .= $this->display_document_form('move', $item_id, $row);
9642
                    break;
9643
                case TOOL_LINK:
9644
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9645
                    $return .= $this->display_link_form('move', $item_id, $row);
9646
                    break;
9647
                case TOOL_HOTPOTATOES:
9648
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9649
                    $return .= $this->display_link_form('move', $item_id, $row);
9650
                    break;
9651
                case TOOL_QUIZ:
9652
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9653
                    $return .= $this->display_quiz_form('move', $item_id, $row);
9654
                    break;
9655
                case TOOL_STUDENTPUBLICATION:
9656
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9657
                    $return .= $this->display_student_publication_form('move', $item_id, $row);
9658
                    break;
9659
                case TOOL_FORUM:
9660
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9661
                    $return .= $this->display_forum_form('move', $item_id, $row);
9662
                    break;
9663
                case TOOL_THREAD:
9664
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9665
                    $return .= $this->display_forum_form('move', $item_id, $row);
9666
                    break;
9667
            }
9668
        }
9669
9670
        return $return;
9671
    }
9672
9673
    /**
9674
     * Return HTML form to allow prerequisites selection.
9675
     *
9676
     * @todo use FormValidator
9677
     *
9678
     * @param int Item ID
9679
     *
9680
     * @return string HTML form
9681
     */
9682
    public function display_item_prerequisites_form($item_id = 0)
9683
    {
9684
        $course_id = api_get_course_int_id();
9685
        $item_id = (int) $item_id;
9686
9687
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9688
9689
        /* Current prerequisite */
9690
        $sql = "SELECT * FROM $tbl_lp_item
9691
                WHERE iid = $item_id";
9692
        $result = Database::query($sql);
9693
        $row = Database::fetch_array($result);
9694
        $prerequisiteId = $row['prerequisite'];
9695
        $return = '<legend>';
9696
        $return .= get_lang('Add/edit prerequisites');
9697
        $return .= '</legend>';
9698
        $return .= '<form method="POST">';
9699
        $return .= '<div class="table-responsive">';
9700
        $return .= '<table class="table table-hover">';
9701
        $return .= '<thead>';
9702
        $return .= '<tr>';
9703
        $return .= '<th>'.get_lang('Prerequisites').'</th>';
9704
        $return .= '<th width="140">'.get_lang('minimum').'</th>';
9705
        $return .= '<th width="140">'.get_lang('maximum').'</th>';
9706
        $return .= '</tr>';
9707
        $return .= '</thead>';
9708
9709
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
9710
        $return .= '<tbody>';
9711
        $return .= '<tr>';
9712
        $return .= '<td colspan="3">';
9713
        $return .= '<div class="radio learnpath"><label for="idnone">';
9714
        $return .= '<input checked="checked" id="idnone" name="prerequisites" type="radio" />';
9715
        $return .= get_lang('none').'</label>';
9716
        $return .= '</div>';
9717
        $return .= '</tr>';
9718
9719
        $sql = "SELECT * FROM $tbl_lp_item
9720
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9721
        $result = Database::query($sql);
9722
9723
        $selectedMinScore = [];
9724
        $selectedMaxScore = [];
9725
        $masteryScore = [];
9726
        while ($row = Database::fetch_array($result)) {
9727
            if ($row['iid'] == $item_id) {
9728
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
9729
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
9730
            }
9731
            $masteryScore[$row['iid']] = $row['mastery_score'];
9732
        }
9733
9734
        $arrLP = $this->getItemsForForm();
9735
        $this->tree_array($arrLP);
9736
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9737
        unset($this->arrMenu);
9738
9739
        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...
9740
            $item = $arrLP[$i];
9741
9742
            if ($item['id'] == $item_id) {
9743
                break;
9744
            }
9745
9746
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
9747
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
9748
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
9749
9750
            $return .= '<tr>';
9751
            $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
9752
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
9753
            $return .= '<label for="id'.$item['id'].'">';
9754
            $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'].'" />';
9755
9756
            $icon_name = str_replace(' ', '', $item['item_type']);
9757
9758
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
9759
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
9760
            } else {
9761
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
9762
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
9763
                } else {
9764
                    $return .= Display::return_icon('folder_document.png');
9765
                }
9766
            }
9767
9768
            $return .= $item['title'].'</label>';
9769
            $return .= '</div>';
9770
            $return .= '</td>';
9771
9772
            if ($item['item_type'] == TOOL_QUIZ) {
9773
                // lets update max_score Tests information depending of the Tests Advanced properties
9774
                $lpItemObj = new LpItem($course_id, $item['id']);
9775
                $exercise = new Exercise($course_id);
9776
                $exercise->read($lpItemObj->path);
9777
                $lpItemObj->max_score = $exercise->get_max_score();
9778
                $lpItemObj->update();
9779
                $item['max_score'] = $lpItemObj->max_score;
9780
9781
                if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
9782
                    // Backwards compatibility with 1.9.x use mastery_score as min value
9783
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
9784
                }
9785
9786
                $return .= '<td>';
9787
                $return .= '<input
9788
                    class="form-control"
9789
                    size="4" maxlength="3"
9790
                    name="min_'.$item['id'].'"
9791
                    type="number"
9792
                    min="0"
9793
                    step="1"
9794
                    max="'.$item['max_score'].'"
9795
                    value="'.$selectedMinScoreValue.'"
9796
                />';
9797
                $return .= '</td>';
9798
                $return .= '<td>';
9799
                $return .= '<input
9800
                    class="form-control"
9801
                    size="4"
9802
                    maxlength="3"
9803
                    name="max_'.$item['id'].'"
9804
                    type="number"
9805
                    min="0"
9806
                    step="1"
9807
                    max="'.$item['max_score'].'"
9808
                    value="'.$selectedMaxScoreValue.'"
9809
                />';
9810
                $return .= '</td>';
9811
            }
9812
9813
            if ($item['item_type'] == TOOL_HOTPOTATOES) {
9814
                $return .= '<td>';
9815
                $return .= '<input
9816
                    size="4"
9817
                    maxlength="3"
9818
                    name="min_'.$item['id'].'"
9819
                    type="number"
9820
                    min="0"
9821
                    step="1"
9822
                    max="'.$item['max_score'].'"
9823
                    value="'.$selectedMinScoreValue.'"
9824
                />';
9825
                $return .= '</td>';
9826
                $return .= '<td>';
9827
                $return .= '<input
9828
                    size="4"
9829
                    maxlength="3"
9830
                    name="max_'.$item['id'].'"
9831
                    type="number"
9832
                    min="0"
9833
                    step="1"
9834
                    max="'.$item['max_score'].'"
9835
                    value="'.$selectedMaxScoreValue.'"
9836
                />';
9837
                $return .= '</td>';
9838
            }
9839
            $return .= '</tr>';
9840
        }
9841
        $return .= '<tr>';
9842
        $return .= '</tr>';
9843
        $return .= '</tbody>';
9844
        $return .= '</table>';
9845
        $return .= '</div>';
9846
        $return .= '<div class="form-group">';
9847
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
9848
            get_lang('Save prerequisites settings').'</button>';
9849
        $return .= '</form>';
9850
9851
        return $return;
9852
    }
9853
9854
    /**
9855
     * Return HTML list to allow prerequisites selection for lp.
9856
     *
9857
     * @return string HTML form
9858
     */
9859
    public function display_lp_prerequisites_list()
9860
    {
9861
        $course_id = api_get_course_int_id();
9862
        $lp_id = $this->lp_id;
9863
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
9864
9865
        // get current prerequisite
9866
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
9867
        $result = Database::query($sql);
9868
        $row = Database::fetch_array($result);
9869
        $prerequisiteId = $row['prerequisite'];
9870
        $session_id = api_get_session_id();
9871
        $session_condition = api_get_session_condition($session_id, true, true);
9872
        $sql = "SELECT * FROM $tbl_lp
9873
                WHERE c_id = $course_id $session_condition
9874
                ORDER BY display_order ";
9875
        $rs = Database::query($sql);
9876
        $return = '';
9877
        $return .= '<select name="prerequisites" class="form-control">';
9878
        $return .= '<option value="0">'.get_lang('none').'</option>';
9879
        if (Database::num_rows($rs) > 0) {
9880
            while ($row = Database::fetch_array($rs)) {
9881
                if ($row['id'] == $lp_id) {
9882
                    continue;
9883
                }
9884
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
9885
            }
9886
        }
9887
        $return .= '</select>';
9888
9889
        return $return;
9890
    }
9891
9892
    /**
9893
     * Creates a list with all the documents in it.
9894
     *
9895
     * @param bool $showInvisibleFiles
9896
     *
9897
     * @throws Exception
9898
     * @throws HTML_QuickForm_Error
9899
     *
9900
     * @return string
9901
     */
9902
    public function get_documents($showInvisibleFiles = false)
9903
    {
9904
        $course_info = api_get_course_info();
9905
        $sessionId = api_get_session_id();
9906
        $documentTree = DocumentManager::get_document_preview(
9907
            $course_info,
9908
            $this->lp_id,
9909
            null,
9910
            $sessionId,
9911
            true,
9912
            null,
9913
            null,
9914
            $showInvisibleFiles,
9915
            true
9916
        );
9917
9918
        $headers = [
9919
            get_lang('Files'),
9920
            get_lang('Create a new document'),
9921
            get_lang('Create read-out text'),
9922
            get_lang('Upload'),
9923
        ];
9924
9925
        $form = new FormValidator(
9926
            'form_upload',
9927
            'POST',
9928
            $this->getCurrentBuildingModeURL(),
9929
            '',
9930
            ['enctype' => 'multipart/form-data']
9931
        );
9932
9933
        $folders = DocumentManager::get_all_document_folders(
9934
            api_get_course_info(),
9935
            0,
9936
            true
9937
        );
9938
9939
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
9940
9941
        DocumentManager::build_directory_selector(
9942
            $folders,
9943
            $lpPathInfo['id'],
9944
            [],
9945
            true,
9946
            $form,
9947
            'directory_parent_id'
9948
        );
9949
9950
        $group = [
9951
            $form->createElement(
9952
                'radio',
9953
                'if_exists',
9954
                get_lang('If file exists:'),
9955
                get_lang('Do nothing'),
9956
                'nothing'
9957
            ),
9958
            $form->createElement(
9959
                'radio',
9960
                'if_exists',
9961
                null,
9962
                get_lang('Overwrite the existing file'),
9963
                'overwrite'
9964
            ),
9965
            $form->createElement(
9966
                'radio',
9967
                'if_exists',
9968
                null,
9969
                get_lang('Rename the uploaded file if it exists'),
9970
                'rename'
9971
            ),
9972
        ];
9973
        $form->addGroup($group, null, get_lang('If file exists:'));
9974
9975
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
9976
        $defaultFileExistsOption = 'rename';
9977
        if (!empty($fileExistsOption)) {
9978
            $defaultFileExistsOption = $fileExistsOption;
9979
        }
9980
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
9981
9982
        // Check box options
9983
        $form->addElement(
9984
            'checkbox',
9985
            'unzip',
9986
            get_lang('Options'),
9987
            get_lang('Uncompress zip')
9988
        );
9989
9990
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
9991
        $form->addMultipleUpload($url);
9992
        $new = $this->display_document_form('add', 0);
9993
        $frmReadOutText = $this->displayFrmReadOutText('add');
9994
        $tabs = Display::tabs(
9995
            $headers,
9996
            [$documentTree, $new, $frmReadOutText, $form->returnForm()],
9997
            'subtab'
9998
        );
9999
10000
        return $tabs;
10001
    }
10002
10003
    /**
10004
     * Creates a list with all the exercises (quiz) in it.
10005
     *
10006
     * @return string
10007
     */
10008
    public function get_exercises()
10009
    {
10010
        $course_id = api_get_course_int_id();
10011
        $session_id = api_get_session_id();
10012
        $userInfo = api_get_user_info();
10013
10014
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
10015
        $condition_session = api_get_session_condition($session_id, true, true);
10016
        $setting = api_get_setting('lp.show_invisible_exercise_in_lp_toc') === 'true';
10017
10018
        $activeCondition = ' active <> -1 ';
10019
        if ($setting) {
10020
            $activeCondition = ' active = 1 ';
10021
        }
10022
10023
        $categoryCondition = '';
10024
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
10025
        if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
10026
            $categoryCondition = " AND exercise_category_id = $categoryId ";
10027
        }
10028
10029
        $keywordCondition = '';
10030
        $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
10031
10032
        if (!empty($keyword)) {
10033
            $keyword = Database::escape_string($keyword);
10034
            $keywordCondition = " AND title LIKE '%$keyword%' ";
10035
        }
10036
10037
        $sql_quiz = "SELECT * FROM $tbl_quiz
10038
                     WHERE
10039
                            c_id = $course_id AND
10040
                            $activeCondition
10041
                            $condition_session
10042
                            $categoryCondition
10043
                            $keywordCondition
10044
                     ORDER BY title ASC";
10045
        $res_quiz = Database::query($sql_quiz);
10046
10047
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
10048
10049
        // Create a search-box
10050
        $form = new FormValidator('search_simple', 'get', $currentUrl);
10051
        $form->addHidden('action', 'add_item');
10052
        $form->addHidden('type', 'step');
10053
        $form->addHidden('lp_id', $this->lp_id);
10054
        $form->addHidden('lp_build_selected', '2');
10055
10056
        $form->addCourseHiddenParams();
10057
        $form->addText(
10058
            'keyword',
10059
            get_lang('Search'),
10060
            false,
10061
            [
10062
                'aria-label' => get_lang('Search'),
10063
            ]
10064
        );
10065
10066
        if (api_get_configuration_value('allow_exercise_categories')) {
10067
            $manager = new ExerciseCategoryManager();
10068
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
10069
            if (!empty($options)) {
10070
                $form->addSelect(
10071
                    'category_id',
10072
                    get_lang('Category'),
10073
                    $options,
10074
                    ['placeholder' => get_lang('Please select an option')]
10075
                );
10076
            }
10077
        }
10078
10079
        $form->addButtonSearch(get_lang('Search'));
10080
        $return = $form->returnForm();
10081
10082
        $return .= '<ul class="lp_resource">';
10083
        $return .= '<li class="lp_resource_element">';
10084
        $return .= Display::return_icon('new_exercice.png');
10085
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
10086
            get_lang('New test').'</a>';
10087
        $return .= '</li>';
10088
10089
        $previewIcon = Display::return_icon(
10090
            'preview_view.png',
10091
            get_lang('Preview')
10092
        );
10093
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
10094
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10095
10096
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
10097
        $repo = Container::getExerciseRepository();
10098
        $courseEntity = api_get_course_entity();
10099
        $sessionEntity = api_get_session_entity();
10100
        while ($row_quiz = Database::fetch_array($res_quiz)) {
10101
            /** @var CQuiz $exercise */
10102
            $exercise = $repo->find($row_quiz['id']);
10103
            $title = strip_tags(
10104
                api_html_entity_decode($row_quiz['title'])
10105
            );
10106
10107
            $visibility = $exercise->isVisible($courseEntity, $sessionEntity);
10108
            /*$visibility = api_get_item_visibility(
10109
                ['real_id' => $course_id],
10110
                TOOL_QUIZ,
10111
                $row_quiz['iid'],
10112
                $session_id
10113
            );*/
10114
10115
            $link = Display::url(
10116
                $previewIcon,
10117
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
10118
                ['target' => '_blank']
10119
            );
10120
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
10121
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
10122
            $return .= $quizIcon;
10123
            $sessionStar = api_get_session_image(
10124
                $row_quiz['session_id'],
10125
                $userInfo['status']
10126
            );
10127
            $return .= Display::url(
10128
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
10129
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['id'].'&lp_id='.$this->lp_id,
10130
                [
10131
                    'class' => $visibility === false ? 'moved text-muted' : 'moved',
10132
                ]
10133
            );
10134
            $return .= '</li>';
10135
        }
10136
10137
        $return .= '</ul>';
10138
10139
        return $return;
10140
    }
10141
10142
    /**
10143
     * Creates a list with all the links in it.
10144
     *
10145
     * @return string
10146
     */
10147
    public function get_links()
10148
    {
10149
        $sessionId = api_get_session_id();
10150
        $repo = Container::getLinkRepository();
10151
10152
        $course = api_get_course_entity();
10153
        $session = api_get_session_entity($sessionId);
10154
        $qb = $repo->getResourcesByCourse($course, $session);
10155
        /** @var CLink[] $links */
10156
        $links = $qb->getQuery()->getResult();
10157
10158
        $selfUrl = api_get_self();
10159
        $courseIdReq = api_get_cidreq();
10160
        $userInfo = api_get_user_info();
10161
10162
        $moveEverywhereIcon = Display::return_icon(
10163
            'move_everywhere.png',
10164
            get_lang('Move'),
10165
            [],
10166
            ICON_SIZE_TINY
10167
        );
10168
10169
        /*$condition_session = api_get_session_condition(
10170
            $session_id,
10171
            true,
10172
            true,
10173
            'link.session_id'
10174
        );
10175
10176
        $sql = "SELECT
10177
                    link.id as link_id,
10178
                    link.title as link_title,
10179
                    link.session_id as link_session_id,
10180
                    link.category_id as category_id,
10181
                    link_category.category_title as category_title
10182
                FROM $tbl_link as link
10183
                LEFT JOIN $linkCategoryTable as link_category
10184
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
10185
                WHERE link.c_id = $course_id $condition_session
10186
                ORDER BY link_category.category_title ASC, link.title ASC";
10187
        $result = Database::query($sql);*/
10188
        $categorizedLinks = [];
10189
        $categories = [];
10190
10191
        foreach ($links as $link) {
10192
            $categoryId = $link->getCategory() !== null ? $link->getCategory()->getIid() : 0;
10193
10194
            if (empty($categoryId)) {
10195
                $categories[0] = get_lang('Uncategorized');
10196
            } else {
10197
                $category = $link->getCategory();
10198
                $categories[$categoryId] = $category->getCategoryTitle();
10199
            }
10200
            $categorizedLinks[$categoryId][$link->getIid()] = $link;
10201
        }
10202
10203
        $linksHtmlCode =
10204
            '<script>
10205
            function toggle_tool(tool, id) {
10206
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
10207
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
10208
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10209
                } else {
10210
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
10211
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
10212
                }
10213
            }
10214
        </script>
10215
10216
        <ul class="lp_resource">
10217
            <li class="lp_resource_element">
10218
                '.Display::return_icon('linksnew.gif').'
10219
                <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').'">'.
10220
                get_lang('Add a link').'
10221
                </a>
10222
            </li>';
10223
10224
        foreach ($categorizedLinks as $categoryId => $links) {
10225
            $linkNodes = null;
10226
            /** @var CLink $link */
10227
            foreach ($links as $key => $link) {
10228
                $title = $link->getTitle();
10229
                $linkSessionId = $link->getSessionId();
10230
10231
                $linkUrl = Display::url(
10232
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10233
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
10234
                    ['target' => '_blank']
10235
                );
10236
10237
                if ($link->isVisible($course, $session)) {
10238
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
10239
                    $linkNodes .=
10240
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
10241
                        <a class="moved" href="#">'.
10242
                            $moveEverywhereIcon.
10243
                        '</a>
10244
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
10245
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
10246
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
10247
                        Security::remove_XSS($title).$sessionStar.$linkUrl.
10248
                        '</a>
10249
                    </li>';
10250
                }
10251
            }
10252
            $linksHtmlCode .=
10253
                '<li>
10254
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
10255
                    <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
10256
                    align="absbottom" />
10257
                </a>
10258
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
10259
            </li>
10260
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
10261
        }
10262
        $linksHtmlCode .= '</ul>';
10263
10264
        return $linksHtmlCode;
10265
    }
10266
10267
    /**
10268
     * Creates a list with all the student publications in it.
10269
     *
10270
     * @return string
10271
     */
10272
    public function get_student_publications()
10273
    {
10274
        $return = '<ul class="lp_resource">';
10275
        $return .= '<li class="lp_resource_element">';
10276
        $return .= Display::return_icon('works_new.gif');
10277
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
10278
            get_lang('Add the Assignments tool to the course').'</a>';
10279
        $return .= '</li>';
10280
10281
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
10282
        $works = getWorkListTeacher(0, 100, null, null, null);
10283
        if (!empty($works)) {
10284
            foreach ($works as $work) {
10285
                $link = Display::url(
10286
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10287
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
10288
                    ['target' => '_blank']
10289
                );
10290
10291
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
10292
                $return .= '<a class="moved" href="#">';
10293
                $return .= Display::return_icon(
10294
                    'move_everywhere.png',
10295
                    get_lang('Move'),
10296
                    [],
10297
                    ICON_SIZE_TINY
10298
                );
10299
                $return .= '</a> ';
10300
10301
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
10302
                $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.'">'.
10303
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
10304
                </a>';
10305
10306
                $return .= '</li>';
10307
            }
10308
        }
10309
10310
        $return .= '</ul>';
10311
10312
        return $return;
10313
    }
10314
10315
    /**
10316
     * Creates a list with all the forums in it.
10317
     *
10318
     * @return string
10319
     */
10320
    public function get_forums()
10321
    {
10322
        require_once '../forum/forumfunction.inc.php';
10323
10324
        $forumCategories = get_forum_categories();
10325
        $forumsInNoCategory = get_forums_in_category(0);
10326
        if (!empty($forumsInNoCategory)) {
10327
            $forumCategories = array_merge(
10328
                $forumCategories,
10329
                [
10330
                    [
10331
                        'cat_id' => 0,
10332
                        'session_id' => 0,
10333
                        'visibility' => 1,
10334
                        'cat_comment' => null,
10335
                    ],
10336
                ]
10337
            );
10338
        }
10339
10340
        $forumList = get_forums();
10341
        $a_forums = [];
10342
        foreach ($forumCategories as $forumCategory) {
10343
            // The forums in this category.
10344
            $forumsInCategory = get_forums_in_category($forumCategory->getIid());
10345
            if (!empty($forumsInCategory)) {
10346
                foreach ($forumList as $forum) {
10347
                    $a_forums[] = $forum;
10348
                }
10349
            }
10350
        }
10351
10352
        $return = '<ul class="lp_resource">';
10353
10354
        // First add link
10355
        $return .= '<li class="lp_resource_element">';
10356
        $return .= Display::return_icon('new_forum.png');
10357
        $return .= Display::url(
10358
            get_lang('Create a new forum'),
10359
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
10360
                'action' => 'add',
10361
                'content' => 'forum',
10362
                'lp_id' => $this->lp_id,
10363
            ]),
10364
            ['title' => get_lang('Create a new forum')]
10365
        );
10366
        $return .= '</li>';
10367
10368
        $return .= '<script>
10369
            function toggle_forum(forum_id) {
10370
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
10371
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
10372
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10373
                } else {
10374
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
10375
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
10376
                }
10377
            }
10378
        </script>';
10379
10380
        foreach ($a_forums as $forum) {
10381
            $forumId = $forum->getIid();
10382
            $title = Security::remove_XSS($forum->getForumTitle());
10383
            $link = Display::url(
10384
                Display::return_icon('preview_view.png', get_lang('Preview')),
10385
                api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forumId,
10386
                ['target' => '_blank']
10387
            );
10388
10389
            $return .= '<li class="lp_resource_element" data_id="'.$forumId.'" data_type="'.TOOL_FORUM.'" title="'.$title.'" >';
10390
            $return .= '<a class="moved" href="#">';
10391
            $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10392
            $return .= ' </a>';
10393
            $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
10394
            $return .= '<a onclick="javascript:toggle_forum('.$forumId.');" style="cursor:hand; vertical-align:middle">
10395
                            <img src="'.Display::returnIconPath('add.png').'" id="forum_'.$forumId.'_opener" align="absbottom" />
10396
                        </a>
10397
                        <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">'.
10398
                $title.' '.$link.'</a>';
10399
10400
            $return .= '</li>';
10401
10402
            $return .= '<div style="display:none" id="forum_'.$forumId.'_content">';
10403
            $a_threads = get_threads($forumId);
10404
            if (is_array($a_threads)) {
10405
                foreach ($a_threads as $thread) {
10406
                    $threadId = $thread->getIid();
10407
                    $link = Display::url(
10408
                        Display::return_icon('preview_view.png', get_lang('Preview')),
10409
                        api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forumId.'&thread='.$threadId,
10410
                        ['target' => '_blank']
10411
                    );
10412
10413
                    $return .= '<li class="lp_resource_element" data_id="'.$thread->getIid().'" data_type="'.TOOL_THREAD.'" title="'.$thread->getThreadTitle().'" >';
10414
                    $return .= '&nbsp;<a class="moved" href="#">';
10415
                    $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10416
                    $return .= ' </a>';
10417
                    $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
10418
                    $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.'">'.
10419
                        Security::remove_XSS($thread->getThreadTitle()).' '.$link.'</a>';
10420
                    $return .= '</li>';
10421
                }
10422
            }
10423
            $return .= '</div>';
10424
        }
10425
        $return .= '</ul>';
10426
10427
        return $return;
10428
    }
10429
10430
    /**
10431
     * // TODO: The output encoding should be equal to the system encoding.
10432
     *
10433
     * Exports the learning path as a SCORM package. This is the main function that
10434
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
10435
     * whole thing and returns the zip.
10436
     *
10437
     * This method needs to be called in PHP5, as it will fail with non-adequate
10438
     * XML package (like the ones for PHP4), and it is *not* a static method, so
10439
     * you need to call it on a learnpath object.
10440
     *
10441
     * @TODO The method might be redefined later on in the scorm class itself to avoid
10442
     * creating a SCORM structure if there is one already. However, if the initial SCORM
10443
     * path has been modified, it should use the generic method here below.
10444
     *
10445
     * @return string Returns the zip package string, or null if error
10446
     */
10447
    public function scormExport()
10448
    {
10449
        api_set_more_memory_and_time_limits();
10450
10451
        $_course = api_get_course_info();
10452
        $course_id = $_course['real_id'];
10453
        // Create the zip handler (this will remain available throughout the method).
10454
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
10455
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
10456
        $temp_dir_short = uniqid('scorm_export', true);
10457
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
10458
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
10459
        $zip_folder = new PclZip($temp_zip_file);
10460
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
10461
        $root_path = $main_path = api_get_path(SYS_PATH);
10462
        $files_cleanup = [];
10463
10464
        // Place to temporarily stash the zip file.
10465
        // create the temp dir if it doesn't exist
10466
        // or do a cleanup before creating the zip file.
10467
        if (!is_dir($temp_zip_dir)) {
10468
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
10469
        } else {
10470
            // Cleanup: Check the temp dir for old files and delete them.
10471
            $handle = opendir($temp_zip_dir);
10472
            while (false !== ($file = readdir($handle))) {
10473
                if ($file != '.' && $file != '..') {
10474
                    unlink("$temp_zip_dir/$file");
10475
                }
10476
            }
10477
            closedir($handle);
10478
        }
10479
        $zip_files = $zip_files_abs = $zip_files_dist = [];
10480
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
10481
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
10482
        ) {
10483
            // Remove the possible . at the end of the path.
10484
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
10485
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
10486
            mkdir(
10487
                $dest_path_to_scorm_folder,
10488
                api_get_permissions_for_new_directories(),
10489
                true
10490
            );
10491
            copyr(
10492
                $current_course_path.'/scorm/'.$this->path,
10493
                $dest_path_to_scorm_folder,
10494
                ['imsmanifest'],
10495
                $zip_files
10496
            );
10497
        }
10498
10499
        // Build a dummy imsmanifest structure.
10500
        // Do not add to the zip yet (we still need it).
10501
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
10502
        // Aggregation Model official document, section "2.3 Content Packaging".
10503
        // We are going to build a UTF-8 encoded manifest.
10504
        // Later we will recode it to the desired (and supported) encoding.
10505
        $xmldoc = new DOMDocument('1.0');
10506
        $root = $xmldoc->createElement('manifest');
10507
        $root->setAttribute('identifier', 'SingleCourseManifest');
10508
        $root->setAttribute('version', '1.1');
10509
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
10510
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
10511
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
10512
        $root->setAttribute(
10513
            'xsi:schemaLocation',
10514
            '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'
10515
        );
10516
        // Build mandatory sub-root container elements.
10517
        $metadata = $xmldoc->createElement('metadata');
10518
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
10519
        $metadata->appendChild($md_schema);
10520
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
10521
        $metadata->appendChild($md_schemaversion);
10522
        $root->appendChild($metadata);
10523
10524
        $organizations = $xmldoc->createElement('organizations');
10525
        $resources = $xmldoc->createElement('resources');
10526
10527
        // Build the only organization we will use in building our learnpaths.
10528
        $organizations->setAttribute('default', 'chamilo_scorm_export');
10529
        $organization = $xmldoc->createElement('organization');
10530
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
10531
        // To set the title of the SCORM entity (=organization), we take the name given
10532
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
10533
        // learning path charset) as it is the encoding that defines how it is stored
10534
        // in the database. Then we convert it to HTML entities again as the "&" character
10535
        // alone is not authorized in XML (must be &amp;).
10536
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
10537
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
10538
        $organization->appendChild($org_title);
10539
        $folder_name = 'document';
10540
10541
        // Removes the learning_path/scorm_folder path when exporting see #4841
10542
        $path_to_remove = '';
10543
        $path_to_replace = '';
10544
        $result = $this->generate_lp_folder($_course);
10545
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
10546
            $path_to_remove = 'document'.$result['dir'];
10547
            $path_to_replace = $folder_name.'/';
10548
        }
10549
10550
        // Fixes chamilo scorm exports
10551
        if ($this->ref === 'chamilo_scorm_export') {
10552
            $path_to_remove = 'scorm/'.$this->path.'/document/';
10553
        }
10554
10555
        // For each element, add it to the imsmanifest structure, then add it to the zip.
10556
        $link_updates = [];
10557
        $links_to_create = [];
10558
        foreach ($this->ordered_items as $index => $itemId) {
10559
            /** @var learnpathItem $item */
10560
            $item = $this->items[$itemId];
10561
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
10562
                // Get included documents from this item.
10563
                if ($item->type === 'sco') {
10564
                    $inc_docs = $item->get_resources_from_source(
10565
                        null,
10566
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
10567
                    );
10568
                } else {
10569
                    $inc_docs = $item->get_resources_from_source();
10570
                }
10571
10572
                // Give a child element <item> to the <organization> element.
10573
                $my_item_id = $item->get_id();
10574
                $my_item = $xmldoc->createElement('item');
10575
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
10576
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
10577
                $my_item->setAttribute('isvisible', 'true');
10578
                // Give a child element <title> to the <item> element.
10579
                $my_title = $xmldoc->createElement(
10580
                    'title',
10581
                    htmlspecialchars(
10582
                        api_utf8_encode($item->get_title()),
10583
                        ENT_QUOTES,
10584
                        'UTF-8'
10585
                    )
10586
                );
10587
                $my_item->appendChild($my_title);
10588
                // Give a child element <adlcp:prerequisites> to the <item> element.
10589
                $my_prereqs = $xmldoc->createElement(
10590
                    'adlcp:prerequisites',
10591
                    $this->get_scorm_prereq_string($my_item_id)
10592
                );
10593
                $my_prereqs->setAttribute('type', 'aicc_script');
10594
                $my_item->appendChild($my_prereqs);
10595
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
10596
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
10597
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
10598
                //$xmldoc->createElement('adlcp:timelimitaction','');
10599
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
10600
                //$xmldoc->createElement('adlcp:datafromlms','');
10601
                // Give a child element <adlcp:masteryscore> to the <item> element.
10602
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10603
                $my_item->appendChild($my_masteryscore);
10604
10605
                // Attach this item to the organization element or hits parent if there is one.
10606
                if (!empty($item->parent) && $item->parent != 0) {
10607
                    $children = $organization->childNodes;
10608
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
10609
                    if (is_object($possible_parent)) {
10610
                        $possible_parent->appendChild($my_item);
10611
                    } else {
10612
                        if ($this->debug > 0) {
10613
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
10614
                        }
10615
                    }
10616
                } else {
10617
                    if ($this->debug > 0) {
10618
                        error_log('No parent');
10619
                    }
10620
                    $organization->appendChild($my_item);
10621
                }
10622
10623
                // Get the path of the file(s) from the course directory root.
10624
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
10625
                $my_xml_file_path = $my_file_path;
10626
                if (!empty($path_to_remove)) {
10627
                    // From docs
10628
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
10629
10630
                    // From quiz
10631
                    if ($this->ref === 'chamilo_scorm_export') {
10632
                        $path_to_remove = 'scorm/'.$this->path.'/';
10633
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
10634
                    }
10635
                }
10636
10637
                $my_sub_dir = dirname($my_file_path);
10638
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10639
                $my_xml_sub_dir = $my_sub_dir;
10640
                // Give a <resource> child to the <resources> element
10641
                $my_resource = $xmldoc->createElement('resource');
10642
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10643
                $my_resource->setAttribute('type', 'webcontent');
10644
                $my_resource->setAttribute('href', $my_xml_file_path);
10645
                // adlcp:scormtype can be either 'sco' or 'asset'.
10646
                if ($item->type === 'sco') {
10647
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
10648
                } else {
10649
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
10650
                }
10651
                // xml:base is the base directory to find the files declared in this resource.
10652
                $my_resource->setAttribute('xml:base', '');
10653
                // Give a <file> child to the <resource> element.
10654
                $my_file = $xmldoc->createElement('file');
10655
                $my_file->setAttribute('href', $my_xml_file_path);
10656
                $my_resource->appendChild($my_file);
10657
10658
                // Dependency to other files - not yet supported.
10659
                $i = 1;
10660
                if ($inc_docs) {
10661
                    foreach ($inc_docs as $doc_info) {
10662
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
10663
                            continue;
10664
                        }
10665
                        $my_dep = $xmldoc->createElement('resource');
10666
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
10667
                        $my_dep->setAttribute('identifier', $res_id);
10668
                        $my_dep->setAttribute('type', 'webcontent');
10669
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
10670
                        $my_dep_file = $xmldoc->createElement('file');
10671
                        // Check type of URL.
10672
                        if ($doc_info[1] == 'remote') {
10673
                            // Remote file. Save url as is.
10674
                            $my_dep_file->setAttribute('href', $doc_info[0]);
10675
                            $my_dep->setAttribute('xml:base', '');
10676
                        } elseif ($doc_info[1] === 'local') {
10677
                            switch ($doc_info[2]) {
10678
                                case 'url':
10679
                                    // Local URL - save path as url for now, don't zip file.
10680
                                    $abs_path = api_get_path(SYS_PATH).
10681
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10682
                                    $current_dir = dirname($abs_path);
10683
                                    $current_dir = str_replace('\\', '/', $current_dir);
10684
                                    $file_path = realpath($abs_path);
10685
                                    $file_path = str_replace('\\', '/', $file_path);
10686
                                    $my_dep_file->setAttribute('href', $file_path);
10687
                                    $my_dep->setAttribute('xml:base', '');
10688
                                    if (strstr($file_path, $main_path) !== false) {
10689
                                        // The calculated real path is really inside Chamilo's root path.
10690
                                        // Reduce file path to what's under the DocumentRoot.
10691
                                        $replace = $file_path;
10692
                                        $file_path = substr($file_path, strlen($root_path) - 1);
10693
                                        $destinationFile = $file_path;
10694
10695
                                        if (strstr($file_path, 'upload/users') !== false) {
10696
                                            $pos = strpos($file_path, 'my_files/');
10697
                                            if ($pos !== false) {
10698
                                                $onlyDirectory = str_replace(
10699
                                                    'upload/users/',
10700
                                                    '',
10701
                                                    substr($file_path, $pos, strlen($file_path))
10702
                                                );
10703
                                            }
10704
                                            $replace = $onlyDirectory;
10705
                                            $destinationFile = $replace;
10706
                                        }
10707
                                        $zip_files_abs[] = $file_path;
10708
                                        $link_updates[$my_file_path][] = [
10709
                                            'orig' => $doc_info[0],
10710
                                            'dest' => $destinationFile,
10711
                                            'replace' => $replace,
10712
                                        ];
10713
                                        $my_dep_file->setAttribute('href', $file_path);
10714
                                        $my_dep->setAttribute('xml:base', '');
10715
                                    } elseif (empty($file_path)) {
10716
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
10717
                                        $file_path = str_replace('//', '/', $file_path);
10718
                                        if (file_exists($file_path)) {
10719
                                            // We get the relative path.
10720
                                            $file_path = substr($file_path, strlen($current_dir));
10721
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
10722
                                            $link_updates[$my_file_path][] = [
10723
                                                'orig' => $doc_info[0],
10724
                                                'dest' => $file_path,
10725
                                            ];
10726
                                            $my_dep_file->setAttribute('href', $file_path);
10727
                                            $my_dep->setAttribute('xml:base', '');
10728
                                        }
10729
                                    }
10730
                                    break;
10731
                                case 'abs':
10732
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
10733
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
10734
                                    $my_dep->setAttribute('xml:base', '');
10735
10736
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
10737
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
10738
                                    $abs_img_path_without_subdir = $doc_info[0];
10739
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
10740
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
10741
                                    if ($pos === 0) {
10742
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
10743
                                    }
10744
10745
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
10746
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
10747
10748
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
10749
                                    $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
10750
                                    // Check if the current document is in that path.
10751
                                    if (strstr($file_path, $cur_path) !== false) {
10752
                                        $destinationFile = substr($file_path, strlen($cur_path));
10753
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
10754
10755
                                        $fileToTest = $cur_path.$my_file_path;
10756
                                        if (!empty($path_to_remove)) {
10757
                                            $fileToTest = str_replace(
10758
                                                $path_to_remove.'/',
10759
                                                $path_to_replace,
10760
                                                $cur_path.$my_file_path
10761
                                            );
10762
                                        }
10763
10764
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
10765
10766
                                        // Put the current document in the zip (this array is the array
10767
                                        // that will manage documents already in the course folder - relative).
10768
                                        $zip_files[] = $filePathNoCoursePart;
10769
                                        // Update the links to the current document in the
10770
                                        // containing document (make them relative).
10771
                                        $link_updates[$my_file_path][] = [
10772
                                            'orig' => $doc_info[0],
10773
                                            'dest' => $destinationFile,
10774
                                            'replace' => $relative_path,
10775
                                        ];
10776
10777
                                        $my_dep_file->setAttribute('href', $file_path);
10778
                                        $my_dep->setAttribute('xml:base', '');
10779
                                    } elseif (strstr($file_path, $main_path) !== false) {
10780
                                        // The calculated real path is really inside Chamilo's root path.
10781
                                        // Reduce file path to what's under the DocumentRoot.
10782
                                        $file_path = substr($file_path, strlen($root_path));
10783
                                        $zip_files_abs[] = $file_path;
10784
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10785
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
10786
                                        $my_dep->setAttribute('xml:base', '');
10787
                                    } elseif (empty($file_path)) {
10788
                                        // Probably this is an image inside "/main" directory
10789
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
10790
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10791
10792
                                        if (file_exists($file_path)) {
10793
                                            if (strstr($file_path, 'main/default_course_document') !== false) {
10794
                                                // We get the relative path.
10795
                                                $pos = strpos($file_path, 'main/default_course_document/');
10796
                                                if ($pos !== false) {
10797
                                                    $onlyDirectory = str_replace(
10798
                                                        'main/default_course_document/',
10799
                                                        '',
10800
                                                        substr($file_path, $pos, strlen($file_path))
10801
                                                    );
10802
                                                }
10803
10804
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
10805
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
10806
                                                $zip_files_abs[] = $fileAbs;
10807
                                                $link_updates[$my_file_path][] = [
10808
                                                    'orig' => $doc_info[0],
10809
                                                    'dest' => $destinationFile,
10810
                                                ];
10811
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
10812
                                                $my_dep->setAttribute('xml:base', '');
10813
                                            }
10814
                                        }
10815
                                    }
10816
                                    break;
10817
                                case 'rel':
10818
                                    // Path relative to the current document.
10819
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
10820
                                    if (substr($doc_info[0], 0, 2) === '..') {
10821
                                        // Relative path going up.
10822
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
10823
                                        $current_dir = str_replace('\\', '/', $current_dir);
10824
                                        $file_path = realpath($current_dir.$doc_info[0]);
10825
                                        $file_path = str_replace('\\', '/', $file_path);
10826
                                        if (strstr($file_path, $main_path) !== false) {
10827
                                            // The calculated real path is really inside Chamilo's root path.
10828
                                            // Reduce file path to what's under the DocumentRoot.
10829
                                            $file_path = substr($file_path, strlen($root_path));
10830
                                            $zip_files_abs[] = $file_path;
10831
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10832
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
10833
                                            $my_dep->setAttribute('xml:base', '');
10834
                                        }
10835
                                    } else {
10836
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
10837
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
10838
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
10839
                                    }
10840
                                    break;
10841
                                default:
10842
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
10843
                                    $my_dep->setAttribute('xml:base', '');
10844
                                    break;
10845
                            }
10846
                        }
10847
                        $my_dep->appendChild($my_dep_file);
10848
                        $resources->appendChild($my_dep);
10849
                        $dependency = $xmldoc->createElement('dependency');
10850
                        $dependency->setAttribute('identifierref', $res_id);
10851
                        $my_resource->appendChild($dependency);
10852
                        $i++;
10853
                    }
10854
                }
10855
                $resources->appendChild($my_resource);
10856
                $zip_files[] = $my_file_path;
10857
            } else {
10858
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
10859
                switch ($item->type) {
10860
                    case TOOL_LINK:
10861
                        $my_item = $xmldoc->createElement('item');
10862
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
10863
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
10864
                        $my_item->setAttribute('isvisible', 'true');
10865
                        // Give a child element <title> to the <item> element.
10866
                        $my_title = $xmldoc->createElement(
10867
                            'title',
10868
                            htmlspecialchars(
10869
                                api_utf8_encode($item->get_title()),
10870
                                ENT_QUOTES,
10871
                                'UTF-8'
10872
                            )
10873
                        );
10874
                        $my_item->appendChild($my_title);
10875
                        // Give a child element <adlcp:prerequisites> to the <item> element.
10876
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
10877
                        $my_prereqs->setAttribute('type', 'aicc_script');
10878
                        $my_item->appendChild($my_prereqs);
10879
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
10880
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
10881
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
10882
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
10883
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
10884
                        //$xmldoc->createElement('adlcp:datafromlms', '');
10885
                        // Give a child element <adlcp:masteryscore> to the <item> element.
10886
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10887
                        $my_item->appendChild($my_masteryscore);
10888
10889
                        // Attach this item to the organization element or its parent if there is one.
10890
                        if (!empty($item->parent) && $item->parent != 0) {
10891
                            $children = $organization->childNodes;
10892
                            for ($i = 0; $i < $children->length; $i++) {
10893
                                $item_temp = $children->item($i);
10894
                                if ($item_temp->nodeName == 'item') {
10895
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
10896
                                        $item_temp->appendChild($my_item);
10897
                                    }
10898
                                }
10899
                            }
10900
                        } else {
10901
                            $organization->appendChild($my_item);
10902
                        }
10903
10904
                        $my_file_path = 'link_'.$item->get_id().'.html';
10905
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
10906
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
10907
                        $rs = Database::query($sql);
10908
                        if ($link = Database::fetch_array($rs)) {
10909
                            $url = $link['url'];
10910
                            $title = stripslashes($link['title']);
10911
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
10912
                            $my_xml_file_path = $my_file_path;
10913
                            $my_sub_dir = dirname($my_file_path);
10914
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10915
                            $my_xml_sub_dir = $my_sub_dir;
10916
                            // Give a <resource> child to the <resources> element.
10917
                            $my_resource = $xmldoc->createElement('resource');
10918
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10919
                            $my_resource->setAttribute('type', 'webcontent');
10920
                            $my_resource->setAttribute('href', $my_xml_file_path);
10921
                            // adlcp:scormtype can be either 'sco' or 'asset'.
10922
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
10923
                            // xml:base is the base directory to find the files declared in this resource.
10924
                            $my_resource->setAttribute('xml:base', '');
10925
                            // give a <file> child to the <resource> element.
10926
                            $my_file = $xmldoc->createElement('file');
10927
                            $my_file->setAttribute('href', $my_xml_file_path);
10928
                            $my_resource->appendChild($my_file);
10929
                            $resources->appendChild($my_resource);
10930
                        }
10931
                        break;
10932
                    case TOOL_QUIZ:
10933
                        $exe_id = $item->path;
10934
                        // Should be using ref when everything will be cleaned up in this regard.
10935
                        $exe = new Exercise();
10936
                        $exe->read($exe_id);
10937
                        $my_item = $xmldoc->createElement('item');
10938
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
10939
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
10940
                        $my_item->setAttribute('isvisible', 'true');
10941
                        // Give a child element <title> to the <item> element.
10942
                        $my_title = $xmldoc->createElement(
10943
                            'title',
10944
                            htmlspecialchars(
10945
                                api_utf8_encode($item->get_title()),
10946
                                ENT_QUOTES,
10947
                                'UTF-8'
10948
                            )
10949
                        );
10950
                        $my_item->appendChild($my_title);
10951
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
10952
                        $my_item->appendChild($my_max_score);
10953
                        // Give a child element <adlcp:prerequisites> to the <item> element.
10954
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
10955
                        $my_prereqs->setAttribute('type', 'aicc_script');
10956
                        $my_item->appendChild($my_prereqs);
10957
                        // Give a child element <adlcp:masteryscore> to the <item> element.
10958
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10959
                        $my_item->appendChild($my_masteryscore);
10960
10961
                        // Attach this item to the organization element or hits parent if there is one.
10962
                        if (!empty($item->parent) && $item->parent != 0) {
10963
                            $children = $organization->childNodes;
10964
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
10965
                            if ($possible_parent) {
10966
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
10967
                                    $possible_parent->appendChild($my_item);
10968
                                }
10969
                            }
10970
                        } else {
10971
                            $organization->appendChild($my_item);
10972
                        }
10973
10974
                        // Get the path of the file(s) from the course directory root
10975
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
10976
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
10977
                        // Write the contents of the exported exercise into a (big) html file
10978
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
10979
                        $scormExercise = new ScormExercise($exe, true);
10980
                        $contents = $scormExercise->export();
10981
10982
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
10983
                        $res = file_put_contents($tmp_file_path, $contents);
10984
                        if ($res === false) {
10985
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
10986
                        }
10987
                        $files_cleanup[] = $tmp_file_path;
10988
                        $my_xml_file_path = $my_file_path;
10989
                        $my_sub_dir = dirname($my_file_path);
10990
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10991
                        $my_xml_sub_dir = $my_sub_dir;
10992
                        // Give a <resource> child to the <resources> element.
10993
                        $my_resource = $xmldoc->createElement('resource');
10994
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10995
                        $my_resource->setAttribute('type', 'webcontent');
10996
                        $my_resource->setAttribute('href', $my_xml_file_path);
10997
                        // adlcp:scormtype can be either 'sco' or 'asset'.
10998
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
10999
                        // xml:base is the base directory to find the files declared in this resource.
11000
                        $my_resource->setAttribute('xml:base', '');
11001
                        // Give a <file> child to the <resource> element.
11002
                        $my_file = $xmldoc->createElement('file');
11003
                        $my_file->setAttribute('href', $my_xml_file_path);
11004
                        $my_resource->appendChild($my_file);
11005
11006
                        // Get included docs.
11007
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
11008
11009
                        // Dependency to other files - not yet supported.
11010
                        $i = 1;
11011
                        foreach ($inc_docs as $doc_info) {
11012
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
11013
                                continue;
11014
                            }
11015
                            $my_dep = $xmldoc->createElement('resource');
11016
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11017
                            $my_dep->setAttribute('identifier', $res_id);
11018
                            $my_dep->setAttribute('type', 'webcontent');
11019
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
11020
                            $my_dep_file = $xmldoc->createElement('file');
11021
                            // Check type of URL.
11022
                            if ($doc_info[1] == 'remote') {
11023
                                // Remote file. Save url as is.
11024
                                $my_dep_file->setAttribute('href', $doc_info[0]);
11025
                                $my_dep->setAttribute('xml:base', '');
11026
                            } elseif ($doc_info[1] == 'local') {
11027
                                switch ($doc_info[2]) {
11028
                                    case 'url': // Local URL - save path as url for now, don't zip file.
11029
                                        // Save file but as local file (retrieve from URL).
11030
                                        $abs_path = api_get_path(SYS_PATH).
11031
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11032
                                        $current_dir = dirname($abs_path);
11033
                                        $current_dir = str_replace('\\', '/', $current_dir);
11034
                                        $file_path = realpath($abs_path);
11035
                                        $file_path = str_replace('\\', '/', $file_path);
11036
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11037
                                        $my_dep->setAttribute('xml:base', '');
11038
                                        if (strstr($file_path, $main_path) !== false) {
11039
                                            // The calculated real path is really inside the chamilo root path.
11040
                                            // Reduce file path to what's under the DocumentRoot.
11041
                                            $file_path = substr($file_path, strlen($root_path));
11042
                                            $zip_files_abs[] = $file_path;
11043
                                            $link_updates[$my_file_path][] = [
11044
                                                'orig' => $doc_info[0],
11045
                                                'dest' => 'document/'.$file_path,
11046
                                            ];
11047
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11048
                                            $my_dep->setAttribute('xml:base', '');
11049
                                        } elseif (empty($file_path)) {
11050
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11051
                                            $file_path = str_replace('//', '/', $file_path);
11052
                                            if (file_exists($file_path)) {
11053
                                                $file_path = substr($file_path, strlen($current_dir));
11054
                                                // We get the relative path.
11055
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11056
                                                $link_updates[$my_file_path][] = [
11057
                                                    'orig' => $doc_info[0],
11058
                                                    'dest' => 'document/'.$file_path,
11059
                                                ];
11060
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11061
                                                $my_dep->setAttribute('xml:base', '');
11062
                                            }
11063
                                        }
11064
                                        break;
11065
                                    case 'abs':
11066
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11067
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11068
                                        $current_dir = str_replace('\\', '/', $current_dir);
11069
                                        $file_path = realpath($doc_info[0]);
11070
                                        $file_path = str_replace('\\', '/', $file_path);
11071
                                        $my_dep_file->setAttribute('href', $file_path);
11072
                                        $my_dep->setAttribute('xml:base', '');
11073
11074
                                        if (strstr($file_path, $main_path) !== false) {
11075
                                            // The calculated real path is really inside the chamilo root path.
11076
                                            // Reduce file path to what's under the DocumentRoot.
11077
                                            $file_path = substr($file_path, strlen($root_path));
11078
                                            $zip_files_abs[] = $file_path;
11079
                                            $link_updates[$my_file_path][] = [
11080
                                                'orig' => $doc_info[0],
11081
                                                'dest' => $file_path,
11082
                                            ];
11083
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11084
                                            $my_dep->setAttribute('xml:base', '');
11085
                                        } elseif (empty($file_path)) {
11086
                                            $docSysPartPath = str_replace(
11087
                                                api_get_path(REL_COURSE_PATH),
11088
                                                '',
11089
                                                $doc_info[0]
11090
                                            );
11091
11092
                                            $docSysPartPathNoCourseCode = str_replace(
11093
                                                $_course['directory'].'/',
11094
                                                '',
11095
                                                $docSysPartPath
11096
                                            );
11097
11098
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
11099
                                            if (file_exists($docSysPath)) {
11100
                                                $file_path = $docSysPartPathNoCourseCode;
11101
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11102
                                                $link_updates[$my_file_path][] = [
11103
                                                    'orig' => $doc_info[0],
11104
                                                    'dest' => $file_path,
11105
                                                ];
11106
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11107
                                                $my_dep->setAttribute('xml:base', '');
11108
                                            }
11109
                                        }
11110
                                        break;
11111
                                    case 'rel':
11112
                                        // Path relative to the current document. Save xml:base as current document's
11113
                                        // directory and save file in zip as subdir.file_path
11114
                                        if (substr($doc_info[0], 0, 2) === '..') {
11115
                                            // Relative path going up.
11116
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11117
                                            $current_dir = str_replace('\\', '/', $current_dir);
11118
                                            $file_path = realpath($current_dir.$doc_info[0]);
11119
                                            $file_path = str_replace('\\', '/', $file_path);
11120
                                            if (strstr($file_path, $main_path) !== false) {
11121
                                                // The calculated real path is really inside Chamilo's root path.
11122
                                                // Reduce file path to what's under the DocumentRoot.
11123
11124
                                                $file_path = substr($file_path, strlen($root_path));
11125
                                                $file_path_dest = $file_path;
11126
11127
                                                // File path is courses/CHAMILO/document/....
11128
                                                $info_file_path = explode('/', $file_path);
11129
                                                if ($info_file_path[0] == 'courses') {
11130
                                                    // Add character "/" in file path.
11131
                                                    $file_path_dest = 'document/'.$file_path;
11132
                                                }
11133
                                                $zip_files_abs[] = $file_path;
11134
11135
                                                $link_updates[$my_file_path][] = [
11136
                                                    'orig' => $doc_info[0],
11137
                                                    'dest' => $file_path_dest,
11138
                                                ];
11139
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11140
                                                $my_dep->setAttribute('xml:base', '');
11141
                                            }
11142
                                        } else {
11143
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11144
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
11145
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11146
                                        }
11147
                                        break;
11148
                                    default:
11149
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
11150
                                        $my_dep->setAttribute('xml:base', '');
11151
                                        break;
11152
                                }
11153
                            }
11154
                            $my_dep->appendChild($my_dep_file);
11155
                            $resources->appendChild($my_dep);
11156
                            $dependency = $xmldoc->createElement('dependency');
11157
                            $dependency->setAttribute('identifierref', $res_id);
11158
                            $my_resource->appendChild($dependency);
11159
                            $i++;
11160
                        }
11161
                        $resources->appendChild($my_resource);
11162
                        $zip_files[] = $my_file_path;
11163
                        break;
11164
                    default:
11165
                        // Get the path of the file(s) from the course directory root
11166
                        $my_file_path = 'non_exportable.html';
11167
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
11168
                        $my_xml_file_path = $my_file_path;
11169
                        $my_sub_dir = dirname($my_file_path);
11170
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11171
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
11172
                        $my_xml_sub_dir = $my_sub_dir;
11173
                        // Give a <resource> child to the <resources> element.
11174
                        $my_resource = $xmldoc->createElement('resource');
11175
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11176
                        $my_resource->setAttribute('type', 'webcontent');
11177
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
11178
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11179
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
11180
                        // xml:base is the base directory to find the files declared in this resource.
11181
                        $my_resource->setAttribute('xml:base', '');
11182
                        // Give a <file> child to the <resource> element.
11183
                        $my_file = $xmldoc->createElement('file');
11184
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
11185
                        $my_resource->appendChild($my_file);
11186
                        $resources->appendChild($my_resource);
11187
                        break;
11188
                }
11189
            }
11190
        }
11191
        $organizations->appendChild($organization);
11192
        $root->appendChild($organizations);
11193
        $root->appendChild($resources);
11194
        $xmldoc->appendChild($root);
11195
11196
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
11197
11198
        // then add the file to the zip, then destroy the file (this is done automatically).
11199
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
11200
        foreach ($zip_files as $file_path) {
11201
            if (empty($file_path)) {
11202
                continue;
11203
            }
11204
11205
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
11206
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
11207
11208
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
11209
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
11210
            }
11211
11212
            $this->create_path($dest_file);
11213
            @copy($filePath, $dest_file);
11214
11215
            // Check if the file needs a link update.
11216
            if (in_array($file_path, array_keys($link_updates))) {
11217
                $string = file_get_contents($dest_file);
11218
                unlink($dest_file);
11219
                foreach ($link_updates[$file_path] as $old_new) {
11220
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11221
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11222
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11223
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11224
                    if (substr($old_new['dest'], -3) === 'flv' &&
11225
                        substr($old_new['dest'], 0, 5) === 'main/'
11226
                    ) {
11227
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11228
                    } elseif (substr($old_new['dest'], -3) === 'flv' &&
11229
                        substr($old_new['dest'], 0, 6) === 'video/'
11230
                    ) {
11231
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
11232
                    }
11233
11234
                    // Fix to avoid problems with default_course_document
11235
                    if (strpos('main/default_course_document', $old_new['dest']) === false) {
11236
                        $newDestination = $old_new['dest'];
11237
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
11238
                            $newDestination = $old_new['replace'];
11239
                        }
11240
                    } else {
11241
                        $newDestination = str_replace('document/', '', $old_new['dest']);
11242
                    }
11243
                    $string = str_replace($old_new['orig'], $newDestination, $string);
11244
11245
                    // Add files inside the HTMLs
11246
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
11247
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
11248
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
11249
                        copy(
11250
                            $sys_course_path.$new_path,
11251
                            $destinationFile
11252
                        );
11253
                    }
11254
                }
11255
                file_put_contents($dest_file, $string);
11256
            }
11257
11258
            if (file_exists($filePath) && $copyAll) {
11259
                $extension = $this->get_extension($filePath);
11260
                if (in_array($extension, ['html', 'html'])) {
11261
                    $containerOrigin = dirname($filePath);
11262
                    $containerDestination = dirname($dest_file);
11263
11264
                    $finder = new Finder();
11265
                    $finder->files()->in($containerOrigin)
11266
                        ->notName('*_DELETED_*')
11267
                        ->exclude('share_folder')
11268
                        ->exclude('chat_files')
11269
                        ->exclude('certificates')
11270
                    ;
11271
11272
                    if (is_dir($containerOrigin) &&
11273
                        is_dir($containerDestination)
11274
                    ) {
11275
                        $fs = new Filesystem();
11276
                        $fs->mirror(
11277
                            $containerOrigin,
11278
                            $containerDestination,
11279
                            $finder
11280
                        );
11281
                    }
11282
                }
11283
            }
11284
        }
11285
11286
        foreach ($zip_files_abs as $file_path) {
11287
            if (empty($file_path)) {
11288
                continue;
11289
            }
11290
11291
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
11292
                continue;
11293
            }
11294
11295
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
11296
            if (strstr($file_path, 'upload/users') !== false) {
11297
                $pos = strpos($file_path, 'my_files/');
11298
                if ($pos !== false) {
11299
                    $onlyDirectory = str_replace(
11300
                        'upload/users/',
11301
                        '',
11302
                        substr($file_path, $pos, strlen($file_path))
11303
                    );
11304
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
11305
                }
11306
            }
11307
11308
            if (strstr($file_path, 'default_course_document/') !== false) {
11309
                $replace = str_replace('/main', '', $file_path);
11310
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
11311
            }
11312
11313
            if (empty($dest_file)) {
11314
                continue;
11315
            }
11316
11317
            $this->create_path($dest_file);
11318
            copy($main_path.$file_path, $dest_file);
11319
            // Check if the file needs a link update.
11320
            if (in_array($file_path, array_keys($link_updates))) {
11321
                $string = file_get_contents($dest_file);
11322
                unlink($dest_file);
11323
                foreach ($link_updates[$file_path] as $old_new) {
11324
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11325
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11326
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11327
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11328
                    if (substr($old_new['dest'], -3) == 'flv' &&
11329
                        substr($old_new['dest'], 0, 5) == 'main/'
11330
                    ) {
11331
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11332
                    }
11333
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
11334
                }
11335
                file_put_contents($dest_file, $string);
11336
            }
11337
        }
11338
11339
        if (is_array($links_to_create)) {
11340
            foreach ($links_to_create as $file => $link) {
11341
                $content = '<!DOCTYPE html><head>
11342
                            <meta charset="'.api_get_language_isocode().'" />
11343
                            <title>'.$link['title'].'</title>
11344
                            </head>
11345
                            <body dir="'.api_get_text_direction().'">
11346
                            <div style="text-align:center">
11347
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
11348
                            </body>
11349
                            </html>';
11350
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
11351
            }
11352
        }
11353
11354
        // Add non exportable message explanation.
11355
        $lang_not_exportable = get_lang('This learning object or activity is not SCORM compliant. That\'s why it is not exportable.');
11356
        $file_content = '<!DOCTYPE html><head>
11357
                        <meta charset="'.api_get_language_isocode().'" />
11358
                        <title>'.$lang_not_exportable.'</title>
11359
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
11360
                        </head>
11361
                        <body dir="'.api_get_text_direction().'">';
11362
        $file_content .=
11363
            <<<EOD
11364
                    <style>
11365
            .error-message {
11366
                font-family: arial, verdana, helvetica, sans-serif;
11367
                border-width: 1px;
11368
                border-style: solid;
11369
                left: 50%;
11370
                margin: 10px auto;
11371
                min-height: 30px;
11372
                padding: 5px;
11373
                right: 50%;
11374
                width: 500px;
11375
                background-color: #FFD1D1;
11376
                border-color: #FF0000;
11377
                color: #000;
11378
            }
11379
        </style>
11380
    <body>
11381
        <div class="error-message">
11382
            $lang_not_exportable
11383
        </div>
11384
    </body>
11385
</html>
11386
EOD;
11387
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
11388
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
11389
        }
11390
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
11391
11392
        // Add the extra files that go along with a SCORM package.
11393
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
11394
11395
        $fs = new Filesystem();
11396
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
11397
11398
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
11399
        $manifest = @$xmldoc->saveXML();
11400
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
11401
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
11402
        $zip_folder->add(
11403
            $archivePath.'/'.$temp_dir_short,
11404
            PCLZIP_OPT_REMOVE_PATH,
11405
            $archivePath.'/'.$temp_dir_short.'/'
11406
        );
11407
11408
        // Clean possible temporary files.
11409
        foreach ($files_cleanup as $file) {
11410
            $res = unlink($file);
11411
            if ($res === false) {
11412
                error_log(
11413
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
11414
                    0
11415
                );
11416
            }
11417
        }
11418
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
11419
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
11420
    }
11421
11422
    /**
11423
     * @param int $lp_id
11424
     *
11425
     * @return bool
11426
     */
11427
    public function scorm_export_to_pdf($lp_id)
11428
    {
11429
        $lp_id = (int) $lp_id;
11430
        $files_to_export = [];
11431
11432
        $sessionId = api_get_session_id();
11433
        $course_data = api_get_course_info($this->cc);
11434
11435
        if (!empty($course_data)) {
11436
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
11437
            $list = self::get_flat_ordered_items_list($lp_id);
11438
            if (!empty($list)) {
11439
                foreach ($list as $item_id) {
11440
                    $item = $this->items[$item_id];
11441
                    switch ($item->type) {
11442
                        case 'document':
11443
                            // Getting documents from a LP with chamilo documents
11444
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
11445
                            // Try loading document from the base course.
11446
                            if (empty($file_data) && !empty($sessionId)) {
11447
                                $file_data = DocumentManager::get_document_data_by_id(
11448
                                    $item->path,
11449
                                    $this->cc,
11450
                                    false,
11451
                                    0
11452
                                );
11453
                            }
11454
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
11455
                            if (file_exists($file_path)) {
11456
                                $files_to_export[] = [
11457
                                    'title' => $item->get_title(),
11458
                                    'path' => $file_path,
11459
                                ];
11460
                            }
11461
                            break;
11462
                        case 'asset': //commes from a scorm package generated by chamilo
11463
                        case 'sco':
11464
                            $file_path = $scorm_path.'/'.$item->path;
11465
                            if (file_exists($file_path)) {
11466
                                $files_to_export[] = [
11467
                                    'title' => $item->get_title(),
11468
                                    'path' => $file_path,
11469
                                ];
11470
                            }
11471
                            break;
11472
                        case 'dir':
11473
                            $files_to_export[] = [
11474
                                'title' => $item->get_title(),
11475
                                'path' => null,
11476
                            ];
11477
                            break;
11478
                    }
11479
                }
11480
            }
11481
11482
            $pdf = new PDF();
11483
            $result = $pdf->html_to_pdf(
11484
                $files_to_export,
11485
                $this->name,
11486
                $this->cc,
11487
                true,
11488
                true,
11489
                true,
11490
                $this->get_name()
11491
            );
11492
11493
            return $result;
11494
        }
11495
11496
        return false;
11497
    }
11498
11499
    /**
11500
     * Temp function to be moved in main_api or the best place around for this.
11501
     * Creates a file path if it doesn't exist.
11502
     *
11503
     * @param string $path
11504
     */
11505
    public function create_path($path)
11506
    {
11507
        $path_bits = explode('/', dirname($path));
11508
11509
        // IS_WINDOWS_OS has been defined in main_api.lib.php
11510
        $path_built = IS_WINDOWS_OS ? '' : '/';
11511
        foreach ($path_bits as $bit) {
11512
            if (!empty($bit)) {
11513
                $new_path = $path_built.$bit;
11514
                if (is_dir($new_path)) {
11515
                    $path_built = $new_path.'/';
11516
                } else {
11517
                    mkdir($new_path, api_get_permissions_for_new_directories());
11518
                    $path_built = $new_path.'/';
11519
                }
11520
            }
11521
        }
11522
    }
11523
11524
    /**
11525
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
11526
     *
11527
     * @return bool The results of the unlink function, or false if there was no image to start with
11528
     */
11529
    public function delete_lp_image()
11530
    {
11531
        $img = $this->get_preview_image();
11532
        if ($img != '') {
11533
            $del_file = $this->get_preview_image_path(null, 'sys');
11534
            if (isset($del_file) && file_exists($del_file)) {
11535
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
11536
                if (file_exists($del_file_2)) {
11537
                    unlink($del_file_2);
11538
                }
11539
                $this->set_preview_image('');
11540
11541
                return @unlink($del_file);
11542
            }
11543
        }
11544
11545
        return false;
11546
    }
11547
11548
    /**
11549
     * Uploads an author image to the upload/learning_path/images directory.
11550
     *
11551
     * @param array    The image array, coming from the $_FILES superglobal
11552
     *
11553
     * @return bool True on success, false on error
11554
     */
11555
    public function upload_image($image_array)
11556
    {
11557
        if (!empty($image_array['name'])) {
11558
            $upload_ok = process_uploaded_file($image_array);
11559
            $has_attachment = true;
11560
        }
11561
11562
        if ($upload_ok && $has_attachment) {
11563
            $courseDir = api_get_course_path().'/upload/learning_path/images';
11564
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
11565
            $updir = $sys_course_path.$courseDir;
11566
            // Try to add an extension to the file if it hasn't one.
11567
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
11568
11569
            if (filter_extension($new_file_name)) {
11570
                $file_extension = explode('.', $image_array['name']);
11571
                $file_extension = strtolower($file_extension[count($file_extension) - 1]);
11572
                $filename = uniqid('');
11573
                $new_file_name = $filename.'.'.$file_extension;
11574
                $new_path = $updir.'/'.$new_file_name;
11575
11576
                // Resize the image.
11577
                $temp = new Image($image_array['tmp_name']);
11578
                $temp->resize(104);
11579
                $result = $temp->send_image($new_path);
11580
11581
                // Storing the image filename.
11582
                if ($result) {
11583
                    $this->set_preview_image($new_file_name);
11584
11585
                    //Resize to 64px to use on course homepage
11586
                    $temp->resize(64);
11587
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
11588
11589
                    return true;
11590
                }
11591
            }
11592
        }
11593
11594
        return false;
11595
    }
11596
11597
    /**
11598
     * @param int    $lp_id
11599
     * @param string $status
11600
     */
11601
    public function set_autolaunch($lp_id, $status)
11602
    {
11603
        $course_id = api_get_course_int_id();
11604
        $lp_id = (int) $lp_id;
11605
        $status = (int) $status;
11606
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
11607
11608
        // Setting everything to autolaunch = 0
11609
        $attributes['autolaunch'] = 0;
11610
        $where = [
11611
            'session_id = ? AND c_id = ? ' => [
11612
                api_get_session_id(),
11613
                $course_id,
11614
            ],
11615
        ];
11616
        Database::update($lp_table, $attributes, $where);
11617
        if ($status == 1) {
11618
            //Setting my lp_id to autolaunch = 1
11619
            $attributes['autolaunch'] = 1;
11620
            $where = [
11621
                'iid = ? AND session_id = ? AND c_id = ?' => [
11622
                    $lp_id,
11623
                    api_get_session_id(),
11624
                    $course_id,
11625
                ],
11626
            ];
11627
            Database::update($lp_table, $attributes, $where);
11628
        }
11629
    }
11630
11631
    /**
11632
     * Gets previous_item_id for the next element of the lp_item table.
11633
     *
11634
     * @author Isaac flores paz
11635
     *
11636
     * @return int Previous item ID
11637
     */
11638
    public function select_previous_item_id()
11639
    {
11640
        $course_id = api_get_course_int_id();
11641
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11642
11643
        // Get the max order of the items
11644
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
11645
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
11646
        $rs_max_order = Database::query($sql);
11647
        $row_max_order = Database::fetch_object($rs_max_order);
11648
        $max_order = $row_max_order->display_order;
11649
        // Get the previous item ID
11650
        $sql = "SELECT iid as previous FROM $table_lp_item
11651
                WHERE
11652
                    c_id = $course_id AND
11653
                    lp_id = ".$this->lp_id." AND
11654
                    display_order = '$max_order' ";
11655
        $rs_max = Database::query($sql);
11656
        $row_max = Database::fetch_object($rs_max);
11657
11658
        // Return the previous item ID
11659
        return $row_max->previous;
11660
    }
11661
11662
    /**
11663
     * Copies an LP.
11664
     */
11665
    public function copy()
11666
    {
11667
        // Course builder
11668
        $cb = new CourseBuilder();
11669
11670
        //Setting tools that will be copied
11671
        $cb->set_tools_to_build(['learnpaths']);
11672
11673
        //Setting elements that will be copied
11674
        $cb->set_tools_specific_id_list(
11675
            ['learnpaths' => [$this->lp_id]]
11676
        );
11677
11678
        $course = $cb->build();
11679
11680
        //Course restorer
11681
        $course_restorer = new CourseRestorer($course);
11682
        $course_restorer->set_add_text_in_items(true);
11683
        $course_restorer->set_tool_copy_settings(
11684
            ['learnpaths' => ['reset_dates' => true]]
11685
        );
11686
        $course_restorer->restore(
11687
            api_get_course_id(),
11688
            api_get_session_id(),
11689
            false,
11690
            false
11691
        );
11692
    }
11693
11694
    /**
11695
     * Verify document size.
11696
     *
11697
     * @param string $s
11698
     *
11699
     * @return bool
11700
     */
11701
    public static function verify_document_size($s)
11702
    {
11703
        $post_max = ini_get('post_max_size');
11704
        if (substr($post_max, -1, 1) == 'M') {
11705
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
11706
        } elseif (substr($post_max, -1, 1) == 'G') {
11707
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
11708
        }
11709
        $upl_max = ini_get('upload_max_filesize');
11710
        if (substr($upl_max, -1, 1) == 'M') {
11711
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
11712
        } elseif (substr($upl_max, -1, 1) == 'G') {
11713
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
11714
        }
11715
11716
        $repo = Container::getDocumentRepository();
11717
        $documents_total_space = $repo->getTotalSpace(api_get_course_int_id());
11718
11719
        $course_max_space = DocumentManager::get_course_quota();
11720
        $total_size = filesize($s) + $documents_total_space;
11721
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
11722
            return true;
11723
        }
11724
11725
        return false;
11726
    }
11727
11728
    /**
11729
     * Clear LP prerequisites.
11730
     */
11731
    public function clear_prerequisites()
11732
    {
11733
        $course_id = $this->get_course_int_id();
11734
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11735
        $lp_id = $this->get_id();
11736
        // Cleaning prerequisites
11737
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
11738
                WHERE c_id = $course_id AND lp_id = $lp_id";
11739
        Database::query($sql);
11740
11741
        // Cleaning mastery score for exercises
11742
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
11743
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
11744
        Database::query($sql);
11745
    }
11746
11747
    public function set_previous_step_as_prerequisite_for_all_items()
11748
    {
11749
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11750
        $course_id = $this->get_course_int_id();
11751
        $lp_id = $this->get_id();
11752
11753
        if (!empty($this->items)) {
11754
            $previous_item_id = null;
11755
            $previous_item_max = 0;
11756
            $previous_item_type = null;
11757
            $last_item_not_dir = null;
11758
            $last_item_not_dir_type = null;
11759
            $last_item_not_dir_max = null;
11760
11761
            foreach ($this->ordered_items as $itemId) {
11762
                $item = $this->getItem($itemId);
11763
                // if there was a previous item... (otherwise jump to set it)
11764
                if (!empty($previous_item_id)) {
11765
                    $current_item_id = $item->get_id(); //save current id
11766
                    if ($item->get_type() != 'dir') {
11767
                        // Current item is not a folder, so it qualifies to get a prerequisites
11768
                        if ($last_item_not_dir_type == 'quiz') {
11769
                            // if previous is quiz, mark its max score as default score to be achieved
11770
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
11771
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
11772
                            Database::query($sql);
11773
                        }
11774
                        // now simply update the prerequisite to set it to the last non-chapter item
11775
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
11776
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
11777
                        Database::query($sql);
11778
                        // record item as 'non-chapter' reference
11779
                        $last_item_not_dir = $item->get_id();
11780
                        $last_item_not_dir_type = $item->get_type();
11781
                        $last_item_not_dir_max = $item->get_max();
11782
                    }
11783
                } else {
11784
                    if ($item->get_type() != 'dir') {
11785
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
11786
                        $last_item_not_dir = $item->get_id();
11787
                        $last_item_not_dir_type = $item->get_type();
11788
                        $last_item_not_dir_max = $item->get_max();
11789
                    }
11790
                }
11791
                // Saving the item as "previous item" for the next loop
11792
                $previous_item_id = $item->get_id();
11793
                $previous_item_max = $item->get_max();
11794
                $previous_item_type = $item->get_type();
11795
            }
11796
        }
11797
    }
11798
11799
    /**
11800
     * @param array $params
11801
     *
11802
     * @throws \Doctrine\ORM\OptimisticLockException
11803
     *
11804
     * @return int
11805
     */
11806
    public static function createCategory($params)
11807
    {
11808
        $item = new CLpCategory();
11809
        $item->setName($params['name']);
11810
        $item->setCId($params['c_id']);
11811
11812
        $repo = Container::getLpCategoryRepository();
11813
        $em = $repo->getEntityManager();
11814
        $em->persist($item);
11815
        $courseEntity = api_get_course_entity(api_get_course_int_id());
11816
11817
        $repo->addResourceToCourse(
11818
            $item,
11819
            ResourceLink::VISIBILITY_PUBLISHED,
11820
            api_get_user_entity(api_get_user_id()),
11821
            $courseEntity,
11822
            api_get_session_entity(),
11823
            api_get_group_entity()
11824
        );
11825
11826
        $em->flush();
11827
11828
        /*api_item_property_update(
11829
            api_get_course_info(),
11830
            TOOL_LEARNPATH_CATEGORY,
11831
            $item->getId(),
11832
            'visible',
11833
            api_get_user_id()
11834
        );*/
11835
11836
        return $item->getId();
11837
    }
11838
11839
    /**
11840
     * @param array $params
11841
     *
11842
     * @throws \Doctrine\ORM\ORMException
11843
     * @throws \Doctrine\ORM\OptimisticLockException
11844
     * @throws \Doctrine\ORM\TransactionRequiredException
11845
     */
11846
    public static function updateCategory($params)
11847
    {
11848
        $em = Database::getManager();
11849
        /** @var CLpCategory $item */
11850
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
11851
        if ($item) {
11852
            $item->setName($params['name']);
11853
            $em->merge($item);
11854
            $em->flush();
11855
        }
11856
    }
11857
11858
    /**
11859
     * @param int $id
11860
     *
11861
     * @throws \Doctrine\ORM\ORMException
11862
     * @throws \Doctrine\ORM\OptimisticLockException
11863
     * @throws \Doctrine\ORM\TransactionRequiredException
11864
     */
11865
    public static function moveUpCategory($id)
11866
    {
11867
        $id = (int) $id;
11868
        $em = Database::getManager();
11869
        /** @var CLpCategory $item */
11870
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11871
        if ($item) {
11872
            $position = $item->getPosition() - 1;
11873
            $item->setPosition($position);
11874
            $em->persist($item);
11875
            $em->flush();
11876
        }
11877
    }
11878
11879
    /**
11880
     * @param int $id
11881
     *
11882
     * @throws \Doctrine\ORM\ORMException
11883
     * @throws \Doctrine\ORM\OptimisticLockException
11884
     * @throws \Doctrine\ORM\TransactionRequiredException
11885
     */
11886
    public static function moveDownCategory($id)
11887
    {
11888
        $id = (int) $id;
11889
        $em = Database::getManager();
11890
        /** @var CLpCategory $item */
11891
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11892
        if ($item) {
11893
            $position = $item->getPosition() + 1;
11894
            $item->setPosition($position);
11895
            $em->persist($item);
11896
            $em->flush();
11897
        }
11898
    }
11899
11900
    /**
11901
     * @param int $courseId
11902
     *
11903
     * @throws \Doctrine\ORM\Query\QueryException
11904
     *
11905
     * @return int|mixed
11906
     */
11907
    public static function getCountCategories($courseId)
11908
    {
11909
        if (empty($courseId)) {
11910
            return 0;
11911
        }
11912
        $em = Database::getManager();
11913
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
11914
        $query->setParameter('id', $courseId);
11915
11916
        return $query->getSingleScalarResult();
11917
    }
11918
11919
    /**
11920
     * @param int $courseId
11921
     *
11922
     * @return mixed
11923
     */
11924
    public static function getCategories($courseId)
11925
    {
11926
        $em = Database::getManager();
11927
11928
        // Using doctrine extensions
11929
        /** @var SortableRepository $repo */
11930
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
11931
        $items = $repo
11932
            ->getBySortableGroupsQuery(['cId' => $courseId])
11933
            ->getResult();
11934
11935
        return $items;
11936
    }
11937
11938
    /**
11939
     * @param int $id
11940
     *
11941
     * @throws \Doctrine\ORM\ORMException
11942
     * @throws \Doctrine\ORM\OptimisticLockException
11943
     * @throws \Doctrine\ORM\TransactionRequiredException
11944
     *
11945
     * @return CLpCategory
11946
     */
11947
    public static function getCategory($id)
11948
    {
11949
        $id = (int) $id;
11950
        $em = Database::getManager();
11951
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11952
11953
        return $item;
11954
    }
11955
11956
    /**
11957
     * @param int $courseId
11958
     *
11959
     * @return array
11960
     */
11961
    public static function getCategoryByCourse($courseId)
11962
    {
11963
        $em = Database::getManager();
11964
        $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
11965
            ['cId' => $courseId]
11966
        );
11967
11968
        return $items;
11969
    }
11970
11971
    /**
11972
     * @param int $id
11973
     *
11974
     * @throws \Doctrine\ORM\ORMException
11975
     * @throws \Doctrine\ORM\OptimisticLockException
11976
     * @throws \Doctrine\ORM\TransactionRequiredException
11977
     *
11978
     * @return mixed
11979
     */
11980
    public static function deleteCategory($id)
11981
    {
11982
        $em = Database::getManager();
11983
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11984
        if ($item) {
11985
            $courseId = $item->getCId();
11986
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
11987
            $query->setParameter('id', $courseId);
11988
            $query->setParameter('catId', $item->getId());
11989
            $lps = $query->getResult();
11990
11991
            // Setting category = 0.
11992
            if ($lps) {
11993
                foreach ($lps as $lpItem) {
11994
                    $lpItem->setCategoryId(0);
11995
                }
11996
            }
11997
11998
            // Removing category.
11999
            $em->remove($item);
12000
            $em->flush();
12001
12002
            $courseInfo = api_get_course_info_by_id($courseId);
12003
            $sessionId = api_get_session_id();
12004
12005
            // Delete link tool
12006
            /*$tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
12007
            $link = 'lp/lp_controller.php?cid='.$courseInfo['real_id'].'&sid='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
12008
            // Delete tools
12009
            $sql = "DELETE FROM $tbl_tool
12010
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
12011
            Database::query($sql);*/
12012
12013
            return true;
12014
        }
12015
12016
        return false;
12017
    }
12018
12019
    /**
12020
     * @param int  $courseId
12021
     * @param bool $addSelectOption
12022
     *
12023
     * @return mixed
12024
     */
12025
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
12026
    {
12027
        $items = self::getCategoryByCourse($courseId);
12028
        $cats = [];
12029
        if ($addSelectOption) {
12030
            $cats = [get_lang('Select a category')];
12031
        }
12032
12033
        if (!empty($items)) {
12034
            foreach ($items as $cat) {
12035
                $cats[$cat->getId()] = $cat->getName();
12036
            }
12037
        }
12038
12039
        return $cats;
12040
    }
12041
12042
    /**
12043
     * @param string $courseCode
12044
     * @param int    $lpId
12045
     * @param int    $user_id
12046
     *
12047
     * @return learnpath
12048
     */
12049
    public static function getLpFromSession($courseCode, $lpId, $user_id)
12050
    {
12051
        $debug = 0;
12052
        $learnPath = null;
12053
        $lpObject = Session::read('lpobject');
12054
        if ($lpObject !== null) {
12055
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
12056
            if ($debug) {
12057
                error_log('getLpFromSession: unserialize');
12058
                error_log('------getLpFromSession------');
12059
                error_log('------unserialize------');
12060
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12061
                error_log("api_get_sessionid: ".api_get_session_id());
12062
            }
12063
        }
12064
12065
        if (!is_object($learnPath)) {
12066
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
12067
            if ($debug) {
12068
                error_log('------getLpFromSession------');
12069
                error_log('getLpFromSession: create new learnpath');
12070
                error_log("create new LP with $courseCode - $lpId - $user_id");
12071
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12072
                error_log("api_get_sessionid: ".api_get_session_id());
12073
            }
12074
        }
12075
12076
        return $learnPath;
12077
    }
12078
12079
    /**
12080
     * @param int $itemId
12081
     *
12082
     * @return learnpathItem|false
12083
     */
12084
    public function getItem($itemId)
12085
    {
12086
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
12087
            return $this->items[$itemId];
12088
        }
12089
12090
        return false;
12091
    }
12092
12093
    /**
12094
     * @return int
12095
     */
12096
    public function getCurrentAttempt()
12097
    {
12098
        $attempt = $this->getItem($this->get_current_item_id());
12099
        if ($attempt) {
12100
            $attemptId = $attempt->get_attempt_id();
12101
12102
            return $attemptId;
12103
        }
12104
12105
        return 0;
12106
    }
12107
12108
    /**
12109
     * @return int
12110
     */
12111
    public function getCategoryId()
12112
    {
12113
        return (int) $this->categoryId;
12114
    }
12115
12116
    /**
12117
     * @param int $categoryId
12118
     *
12119
     * @return bool
12120
     */
12121
    public function setCategoryId($categoryId)
12122
    {
12123
        $this->categoryId = (int) $categoryId;
12124
        $table = Database::get_course_table(TABLE_LP_MAIN);
12125
        $lp_id = $this->get_id();
12126
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
12127
                WHERE iid = $lp_id";
12128
        Database::query($sql);
12129
12130
        return true;
12131
    }
12132
12133
    /**
12134
     * Get whether this is a learning path with the possibility to subscribe
12135
     * users or not.
12136
     *
12137
     * @return int
12138
     */
12139
    public function getSubscribeUsers()
12140
    {
12141
        return $this->subscribeUsers;
12142
    }
12143
12144
    /**
12145
     * Set whether this is a learning path with the possibility to subscribe
12146
     * users or not.
12147
     *
12148
     * @param int $value (0 = false, 1 = true)
12149
     *
12150
     * @return bool
12151
     */
12152
    public function setSubscribeUsers($value)
12153
    {
12154
        $this->subscribeUsers = (int) $value;
12155
        $table = Database::get_course_table(TABLE_LP_MAIN);
12156
        $lp_id = $this->get_id();
12157
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
12158
                WHERE iid = $lp_id";
12159
        Database::query($sql);
12160
12161
        return true;
12162
    }
12163
12164
    /**
12165
     * Calculate the count of stars for a user in this LP
12166
     * This calculation is based on the following rules:
12167
     * - the student gets one star when he gets to 50% of the learning path
12168
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
12169
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
12170
     * - the student gets the final star when the score for the *last* test is >= 80%.
12171
     *
12172
     * @param int $sessionId Optional. The session ID
12173
     *
12174
     * @return int The count of stars
12175
     */
12176
    public function getCalculateStars($sessionId = 0)
12177
    {
12178
        $stars = 0;
12179
        $progress = self::getProgress(
12180
            $this->lp_id,
12181
            $this->user_id,
12182
            $this->course_int_id,
12183
            $sessionId
12184
        );
12185
12186
        if ($progress >= 50) {
12187
            $stars++;
12188
        }
12189
12190
        // Calculate stars chapters evaluation
12191
        $exercisesItems = $this->getExercisesItems();
12192
12193
        if (!empty($exercisesItems)) {
12194
            $totalResult = 0;
12195
12196
            foreach ($exercisesItems as $exerciseItem) {
12197
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12198
                    $this->user_id,
12199
                    $exerciseItem->path,
12200
                    $this->course_int_id,
12201
                    $sessionId,
12202
                    $this->lp_id,
12203
                    $exerciseItem->db_id
12204
                );
12205
12206
                $exerciseResultInfo = end($exerciseResultInfo);
12207
12208
                if (!$exerciseResultInfo) {
12209
                    continue;
12210
                }
12211
12212
                if (!empty($exerciseResultInfo['max_score'])) {
12213
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
12214
                } else {
12215
                    $exerciseResult = 0;
12216
                }
12217
                $totalResult += $exerciseResult;
12218
            }
12219
12220
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
12221
12222
            if ($totalExerciseAverage >= 50) {
12223
                $stars++;
12224
            }
12225
12226
            if ($totalExerciseAverage >= 80) {
12227
                $stars++;
12228
            }
12229
        }
12230
12231
        // Calculate star for final evaluation
12232
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12233
12234
        if (!empty($finalEvaluationItem)) {
12235
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12236
                $this->user_id,
12237
                $finalEvaluationItem->path,
12238
                $this->course_int_id,
12239
                $sessionId,
12240
                $this->lp_id,
12241
                $finalEvaluationItem->db_id
12242
            );
12243
12244
            $evaluationResultInfo = end($evaluationResultInfo);
12245
12246
            if ($evaluationResultInfo) {
12247
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
12248
12249
                if ($evaluationResult >= 80) {
12250
                    $stars++;
12251
                }
12252
            }
12253
        }
12254
12255
        return $stars;
12256
    }
12257
12258
    /**
12259
     * Get the items of exercise type.
12260
     *
12261
     * @return array The items. Otherwise return false
12262
     */
12263
    public function getExercisesItems()
12264
    {
12265
        $exercises = [];
12266
        foreach ($this->items as $item) {
12267
            if ($item->type != 'quiz') {
12268
                continue;
12269
            }
12270
            $exercises[] = $item;
12271
        }
12272
12273
        array_pop($exercises);
12274
12275
        return $exercises;
12276
    }
12277
12278
    /**
12279
     * Get the item of exercise type (evaluation type).
12280
     *
12281
     * @return array The final evaluation. Otherwise return false
12282
     */
12283
    public function getFinalEvaluationItem()
12284
    {
12285
        $exercises = [];
12286
        foreach ($this->items as $item) {
12287
            if ($item->type != 'quiz') {
12288
                continue;
12289
            }
12290
12291
            $exercises[] = $item;
12292
        }
12293
12294
        return array_pop($exercises);
12295
    }
12296
12297
    /**
12298
     * Calculate the total points achieved for the current user in this learning path.
12299
     *
12300
     * @param int $sessionId Optional. The session Id
12301
     *
12302
     * @return int
12303
     */
12304
    public function getCalculateScore($sessionId = 0)
12305
    {
12306
        // Calculate stars chapters evaluation
12307
        $exercisesItems = $this->getExercisesItems();
12308
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12309
        $totalExercisesResult = 0;
12310
        $totalEvaluationResult = 0;
12311
12312
        if ($exercisesItems !== false) {
12313
            foreach ($exercisesItems as $exerciseItem) {
12314
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12315
                    $this->user_id,
12316
                    $exerciseItem->path,
12317
                    $this->course_int_id,
12318
                    $sessionId,
12319
                    $this->lp_id,
12320
                    $exerciseItem->db_id
12321
                );
12322
12323
                $exerciseResultInfo = end($exerciseResultInfo);
12324
12325
                if (!$exerciseResultInfo) {
12326
                    continue;
12327
                }
12328
12329
                $totalExercisesResult += $exerciseResultInfo['score'];
12330
            }
12331
        }
12332
12333
        if (!empty($finalEvaluationItem)) {
12334
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12335
                $this->user_id,
12336
                $finalEvaluationItem->path,
12337
                $this->course_int_id,
12338
                $sessionId,
12339
                $this->lp_id,
12340
                $finalEvaluationItem->db_id
12341
            );
12342
12343
            $evaluationResultInfo = end($evaluationResultInfo);
12344
12345
            if ($evaluationResultInfo) {
12346
                $totalEvaluationResult += $evaluationResultInfo['score'];
12347
            }
12348
        }
12349
12350
        return $totalExercisesResult + $totalEvaluationResult;
12351
    }
12352
12353
    /**
12354
     * Check if URL is not allowed to be show in a iframe.
12355
     *
12356
     * @param string $src
12357
     *
12358
     * @return string
12359
     */
12360
    public function fixBlockedLinks($src)
12361
    {
12362
        $urlInfo = parse_url($src);
12363
12364
        $platformProtocol = 'https';
12365
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
12366
            $platformProtocol = 'http';
12367
        }
12368
12369
        $protocolFixApplied = false;
12370
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
12371
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
12372
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
12373
12374
        if ($platformProtocol != $scheme) {
12375
            Session::write('x_frame_source', $src);
12376
            $src = 'blank.php?error=x_frames_options';
12377
            $protocolFixApplied = true;
12378
        }
12379
12380
        if ($protocolFixApplied == false) {
12381
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
12382
                // Check X-Frame-Options
12383
                $ch = curl_init();
12384
                $options = [
12385
                    CURLOPT_URL => $src,
12386
                    CURLOPT_RETURNTRANSFER => true,
12387
                    CURLOPT_HEADER => true,
12388
                    CURLOPT_FOLLOWLOCATION => true,
12389
                    CURLOPT_ENCODING => "",
12390
                    CURLOPT_AUTOREFERER => true,
12391
                    CURLOPT_CONNECTTIMEOUT => 120,
12392
                    CURLOPT_TIMEOUT => 120,
12393
                    CURLOPT_MAXREDIRS => 10,
12394
                ];
12395
12396
                $proxySettings = api_get_configuration_value('proxy_settings');
12397
                if (!empty($proxySettings) &&
12398
                    isset($proxySettings['curl_setopt_array'])
12399
                ) {
12400
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
12401
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
12402
                }
12403
12404
                curl_setopt_array($ch, $options);
12405
                $response = curl_exec($ch);
12406
                $httpCode = curl_getinfo($ch);
12407
                $headers = substr($response, 0, $httpCode['header_size']);
12408
12409
                $error = false;
12410
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
12411
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
12412
                ) {
12413
                    $error = true;
12414
                }
12415
12416
                if ($error) {
12417
                    Session::write('x_frame_source', $src);
12418
                    $src = 'blank.php?error=x_frames_options';
12419
                }
12420
            }
12421
        }
12422
12423
        return $src;
12424
    }
12425
12426
    /**
12427
     * Check if this LP has a created forum in the basis course.
12428
     *
12429
     * @return bool
12430
     */
12431
    public function lpHasForum()
12432
    {
12433
        $forumTable = Database::get_course_table(TABLE_FORUM);
12434
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12435
12436
        $fakeFrom = "
12437
            $forumTable f
12438
            INNER JOIN $itemProperty ip
12439
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
12440
        ";
12441
12442
        $resultData = Database::select(
12443
            'COUNT(f.iid) AS qty',
12444
            $fakeFrom,
12445
            [
12446
                'where' => [
12447
                    'ip.visibility != ? AND ' => 2,
12448
                    'ip.tool = ? AND ' => TOOL_FORUM,
12449
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12450
                    'f.lp_id = ?' => intval($this->lp_id),
12451
                ],
12452
            ],
12453
            'first'
12454
        );
12455
12456
        return $resultData['qty'] > 0;
12457
    }
12458
12459
    /**
12460
     * Get the forum for this learning path.
12461
     *
12462
     * @param int $sessionId
12463
     *
12464
     * @return array
12465
     */
12466
    public function getForum($sessionId = 0)
12467
    {
12468
        $repo = Container::getForumRepository();
12469
12470
        $course = api_get_course_entity();
12471
        $session = api_get_session_entity($sessionId);
12472
        $qb = $repo->getResourcesByCourse($course, $session);
12473
12474
        return $qb->getQuery()->getResult();
12475
    }
12476
12477
    /**
12478
     * Create a forum for this learning path.
12479
     *
12480
     * @param int $forumCategoryId
12481
     *
12482
     * @return int The forum ID if was created. Otherwise return false
12483
     */
12484
    public function createForum($forumCategoryId)
12485
    {
12486
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
12487
12488
        $forumId = store_forum(
12489
            [
12490
                'lp_id' => $this->lp_id,
12491
                'forum_title' => $this->name,
12492
                'forum_comment' => null,
12493
                'forum_category' => (int) $forumCategoryId,
12494
                'students_can_edit_group' => ['students_can_edit' => 0],
12495
                'allow_new_threads_group' => ['allow_new_threads' => 0],
12496
                'default_view_type_group' => ['default_view_type' => 'flat'],
12497
                'group_forum' => 0,
12498
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
12499
            ],
12500
            [],
12501
            true
12502
        );
12503
12504
        return $forumId;
12505
    }
12506
12507
    /**
12508
     * Get the LP Final Item form.
12509
     *
12510
     * @throws Exception
12511
     * @throws HTML_QuickForm_Error
12512
     *
12513
     * @return string
12514
     */
12515
    public function getFinalItemForm()
12516
    {
12517
        $finalItem = $this->getFinalItem();
12518
        $title = '';
12519
12520
        if ($finalItem) {
12521
            $title = $finalItem->get_title();
12522
            $buttonText = get_lang('Save');
12523
            $content = $this->getSavedFinalItem();
12524
        } else {
12525
            $buttonText = get_lang('Add this document to the course');
12526
            $content = $this->getFinalItemTemplate();
12527
        }
12528
12529
        $courseInfo = api_get_course_info();
12530
        $result = $this->generate_lp_folder($courseInfo);
12531
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
12532
        $relative_prefix = '../../';
12533
12534
        $editorConfig = [
12535
            'ToolbarSet' => 'LearningPathDocuments',
12536
            'Width' => '100%',
12537
            'Height' => '500',
12538
            'FullPage' => true,
12539
            'CreateDocumentDir' => $relative_prefix,
12540
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
12541
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
12542
        ];
12543
12544
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
12545
            'type' => 'document',
12546
            'lp_id' => $this->lp_id,
12547
        ]);
12548
12549
        $form = new FormValidator('final_item', 'POST', $url);
12550
        $form->addText('title', get_lang('Title'));
12551
        $form->addButtonSave($buttonText);
12552
        $form->addHtml(
12553
            Display::return_message(
12554
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
12555
                'normal',
12556
                false
12557
            )
12558
        );
12559
12560
        $renderer = $form->defaultRenderer();
12561
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
12562
12563
        $form->addHtmlEditor(
12564
            'content_lp_certificate',
12565
            null,
12566
            true,
12567
            false,
12568
            $editorConfig,
12569
            true
12570
        );
12571
        $form->addHidden('action', 'add_final_item');
12572
        $form->addHidden('path', Session::read('pathItem'));
12573
        $form->addHidden('previous', $this->get_last());
12574
        $form->setDefaults(
12575
            ['title' => $title, 'content_lp_certificate' => $content]
12576
        );
12577
12578
        if ($form->validate()) {
12579
            $values = $form->exportValues();
12580
            $lastItemId = $this->getLastInFirstLevel();
12581
12582
            if (!$finalItem) {
12583
                $documentId = $this->create_document(
12584
                    $this->course_info,
12585
                    $values['content_lp_certificate'],
12586
                    $values['title']
12587
                );
12588
                $this->add_item(
12589
                    0,
12590
                    $lastItemId,
12591
                    'final_item',
12592
                    $documentId,
12593
                    $values['title'],
12594
                    ''
12595
                );
12596
12597
                Display::addFlash(
12598
                    Display::return_message(get_lang('Added'))
12599
                );
12600
            } else {
12601
                $this->edit_document($this->course_info);
12602
            }
12603
        }
12604
12605
        return $form->returnForm();
12606
    }
12607
12608
    /**
12609
     * Check if the current lp item is first, both, last or none from lp list.
12610
     *
12611
     * @param int $currentItemId
12612
     *
12613
     * @return string
12614
     */
12615
    public function isFirstOrLastItem($currentItemId)
12616
    {
12617
        $lpItemId = [];
12618
        $typeListNotToVerify = self::getChapterTypes();
12619
12620
        // Using get_toc() function instead $this->items because returns the correct order of the items
12621
        foreach ($this->get_toc() as $item) {
12622
            if (!in_array($item['type'], $typeListNotToVerify)) {
12623
                $lpItemId[] = $item['id'];
12624
            }
12625
        }
12626
12627
        $lastLpItemIndex = count($lpItemId) - 1;
12628
        $position = array_search($currentItemId, $lpItemId);
12629
12630
        switch ($position) {
12631
            case 0:
12632
                if (!$lastLpItemIndex) {
12633
                    $answer = 'both';
12634
                    break;
12635
                }
12636
12637
                $answer = 'first';
12638
                break;
12639
            case $lastLpItemIndex:
12640
                $answer = 'last';
12641
                break;
12642
            default:
12643
                $answer = 'none';
12644
        }
12645
12646
        return $answer;
12647
    }
12648
12649
    /**
12650
     * Get whether this is a learning path with the accumulated SCORM time or not.
12651
     *
12652
     * @return int
12653
     */
12654
    public function getAccumulateScormTime()
12655
    {
12656
        return $this->accumulateScormTime;
12657
    }
12658
12659
    /**
12660
     * Set whether this is a learning path with the accumulated SCORM time or not.
12661
     *
12662
     * @param int $value (0 = false, 1 = true)
12663
     *
12664
     * @return bool Always returns true
12665
     */
12666
    public function setAccumulateScormTime($value)
12667
    {
12668
        $this->accumulateScormTime = (int) $value;
12669
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
12670
        $lp_id = $this->get_id();
12671
        $sql = "UPDATE $lp_table
12672
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
12673
                WHERE iid = $lp_id";
12674
        Database::query($sql);
12675
12676
        return true;
12677
    }
12678
12679
    /**
12680
     * Returns an HTML-formatted link to a resource, to incorporate directly into
12681
     * the new learning path tool.
12682
     *
12683
     * The function is a big switch on tool type.
12684
     * In each case, we query the corresponding table for information and build the link
12685
     * with that information.
12686
     *
12687
     * @author Yannick Warnier <[email protected]> - rebranding based on
12688
     * previous work (display_addedresource_link_in_learnpath())
12689
     *
12690
     * @param int $course_id      Course code
12691
     * @param int $learningPathId The learning path ID (in lp table)
12692
     * @param int $id_in_path     the unique index in the items table
12693
     * @param int $lpViewId
12694
     *
12695
     * @return string
12696
     */
12697
    public static function rl_get_resource_link_for_learnpath(
12698
        $course_id,
12699
        $learningPathId,
12700
        $id_in_path,
12701
        $lpViewId
12702
    ) {
12703
        $session_id = api_get_session_id();
12704
        $course_info = api_get_course_info_by_id($course_id);
12705
12706
        $learningPathId = (int) $learningPathId;
12707
        $id_in_path = (int) $id_in_path;
12708
        $lpViewId = (int) $lpViewId;
12709
12710
        $em = Database::getManager();
12711
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
12712
12713
        /** @var CLpItem $rowItem */
12714
        $rowItem = $lpItemRepo->findOneBy([
12715
            'cId' => $course_id,
12716
            'lpId' => $learningPathId,
12717
            'iid' => $id_in_path,
12718
        ]);
12719
12720
        if (!$rowItem) {
12721
            // Try one more time with "id"
12722
            /** @var CLpItem $rowItem */
12723
            $rowItem = $lpItemRepo->findOneBy([
12724
                'cId' => $course_id,
12725
                'lpId' => $learningPathId,
12726
                'id' => $id_in_path,
12727
            ]);
12728
12729
            if (!$rowItem) {
12730
                return -1;
12731
            }
12732
        }
12733
12734
        $type = $rowItem->getItemType();
12735
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
12736
        $main_dir_path = api_get_path(WEB_CODE_PATH);
12737
        //$main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
12738
        $link = '';
12739
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
12740
12741
        switch ($type) {
12742
            case 'dir':
12743
                return $main_dir_path.'lp/blank.php';
12744
            case TOOL_CALENDAR_EVENT:
12745
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
12746
            case TOOL_ANNOUNCEMENT:
12747
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
12748
            case TOOL_LINK:
12749
                $linkInfo = Link::getLinkInfo($id);
12750
                if (isset($linkInfo['url'])) {
12751
                    return $linkInfo['url'];
12752
                }
12753
12754
                return '';
12755
            case TOOL_QUIZ:
12756
                if (empty($id)) {
12757
                    return '';
12758
                }
12759
12760
                // Get the lp_item_view with the highest view_count.
12761
                $learnpathItemViewResult = $em
12762
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
12763
                    ->findBy(
12764
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
12765
                        ['viewCount' => 'DESC'],
12766
                        1
12767
                    );
12768
                /** @var CLpItemView $learnpathItemViewData */
12769
                $learnpathItemViewData = current($learnpathItemViewResult);
12770
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
12771
12772
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
12773
                    .http_build_query([
12774
                        'lp_init' => 1,
12775
                        'learnpath_item_view_id' => $learnpathItemViewId,
12776
                        'learnpath_id' => $learningPathId,
12777
                        'learnpath_item_id' => $id_in_path,
12778
                        'exerciseId' => $id,
12779
                    ]);
12780
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
12781
                /*$TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
12782
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
12783
                $myrow = Database::fetch_array($result);
12784
                $path = $myrow['path'];
12785
12786
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
12787
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
12788
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;*/
12789
            case TOOL_FORUM:
12790
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
12791
            case TOOL_THREAD:
12792
                // forum post
12793
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
12794
                if (empty($id)) {
12795
                    return '';
12796
                }
12797
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
12798
                $result = Database::query($sql);
12799
                $myrow = Database::fetch_array($result);
12800
12801
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
12802
                    .$extraParams;
12803
            case TOOL_POST:
12804
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12805
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
12806
                $myrow = Database::fetch_array($result);
12807
12808
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
12809
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
12810
            case TOOL_READOUT_TEXT:
12811
                return api_get_path(WEB_CODE_PATH).
12812
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
12813
            case TOOL_DOCUMENT:
12814
                $repo = Container::getDocumentRepository();
12815
                $document = $repo->find($rowItem->getPath());
12816
                $file = $repo->getResourceFileUrl($document, [], UrlGeneratorInterface::ABSOLUTE_URL);
12817
12818
                return $file;
12819
12820
                $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...
12821
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v'];
12822
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
12823
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
12824
12825
                $openmethod = 2;
12826
                $officedoc = false;
12827
                Session::write('openmethod', $openmethod);
12828
                Session::write('officedoc', $officedoc);
12829
12830
                if ($showDirectUrl) {
12831
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
12832
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
12833
                        if (Link::isPdfLink($file)) {
12834
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
12835
12836
                            return $pdfUrl;
12837
                        }
12838
                    }
12839
12840
                    return $file;
12841
                }
12842
12843
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
12844
            case TOOL_LP_FINAL_ITEM:
12845
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
12846
                    .$extraParams;
12847
            case 'assignments':
12848
                return $main_dir_path.'work/work.php?'.$extraParams;
12849
            case TOOL_DROPBOX:
12850
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
12851
            case 'introduction_text': //DEPRECATED
12852
                return '';
12853
            case TOOL_COURSE_DESCRIPTION:
12854
                return $main_dir_path.'course_description?'.$extraParams;
12855
            case TOOL_GROUP:
12856
                return $main_dir_path.'group/group.php?'.$extraParams;
12857
            case TOOL_USER:
12858
                return $main_dir_path.'user/user.php?'.$extraParams;
12859
            case TOOL_STUDENTPUBLICATION:
12860
                if (!empty($rowItem->getPath())) {
12861
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
12862
                }
12863
12864
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
12865
        }
12866
12867
        return $link;
12868
    }
12869
12870
    /**
12871
     * Gets the name of a resource (generally used in learnpath when no name is provided).
12872
     *
12873
     * @author Yannick Warnier <[email protected]>
12874
     *
12875
     * @param string $course_code    Course code
12876
     * @param int    $learningPathId
12877
     * @param int    $id_in_path     The resource ID
12878
     *
12879
     * @return string
12880
     */
12881
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
12882
    {
12883
        $_course = api_get_course_info($course_code);
12884
        if (empty($_course)) {
12885
            return '';
12886
        }
12887
        $course_id = $_course['real_id'];
12888
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12889
        $learningPathId = (int) $learningPathId;
12890
        $id_in_path = (int) $id_in_path;
12891
12892
        $sql = "SELECT item_type, title, ref
12893
                FROM $tbl_lp_item
12894
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
12895
        $res_item = Database::query($sql);
12896
12897
        if (Database::num_rows($res_item) < 1) {
12898
            return '';
12899
        }
12900
        $row_item = Database::fetch_array($res_item);
12901
        $type = strtolower($row_item['item_type']);
12902
        $id = $row_item['ref'];
12903
        $output = '';
12904
12905
        switch ($type) {
12906
            case TOOL_CALENDAR_EVENT:
12907
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
12908
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
12909
                $myrow = Database::fetch_array($result);
12910
                $output = $myrow['title'];
12911
                break;
12912
            case TOOL_ANNOUNCEMENT:
12913
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
12914
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
12915
                $myrow = Database::fetch_array($result);
12916
                $output = $myrow['title'];
12917
                break;
12918
            case TOOL_LINK:
12919
                // Doesn't take $target into account.
12920
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
12921
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
12922
                $myrow = Database::fetch_array($result);
12923
                $output = $myrow['title'];
12924
                break;
12925
            case TOOL_QUIZ:
12926
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
12927
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
12928
                $myrow = Database::fetch_array($result);
12929
                $output = $myrow['title'];
12930
                break;
12931
            case TOOL_FORUM:
12932
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
12933
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
12934
                $myrow = Database::fetch_array($result);
12935
                $output = $myrow['forum_name'];
12936
                break;
12937
            case TOOL_THREAD:
12938
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12939
                // Grabbing the title of the post.
12940
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
12941
                $result_title = Database::query($sql_title);
12942
                $myrow_title = Database::fetch_array($result_title);
12943
                $output = $myrow_title['post_title'];
12944
                break;
12945
            case TOOL_POST:
12946
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12947
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
12948
                $result = Database::query($sql);
12949
                $post = Database::fetch_array($result);
12950
                $output = $post['post_title'];
12951
                break;
12952
            case 'dir':
12953
            case TOOL_DOCUMENT:
12954
                $title = $row_item['title'];
12955
                $output = '-';
12956
                if (!empty($title)) {
12957
                    $output = $title;
12958
                }
12959
                break;
12960
            case 'hotpotatoes':
12961
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
12962
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
12963
                $myrow = Database::fetch_array($result);
12964
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
12965
                $last = count($pathname) - 1; // Making a correct name for the link.
12966
                $filename = $pathname[$last]; // Making a correct name for the link.
12967
                $myrow['path'] = rawurlencode($myrow['path']);
12968
                $output = $filename;
12969
                break;
12970
        }
12971
12972
        return stripslashes($output);
12973
    }
12974
12975
    /**
12976
     * Get the parent names for the current item.
12977
     *
12978
     * @param int $newItemId Optional. The item ID
12979
     *
12980
     * @return array
12981
     */
12982
    public function getCurrentItemParentNames($newItemId = 0)
12983
    {
12984
        $newItemId = $newItemId ?: $this->get_current_item_id();
12985
        $return = [];
12986
        $item = $this->getItem($newItemId);
12987
        $parent = $this->getItem($item->get_parent());
12988
12989
        while ($parent) {
12990
            $return[] = $parent->get_title();
12991
            $parent = $this->getItem($parent->get_parent());
12992
        }
12993
12994
        return array_reverse($return);
12995
    }
12996
12997
    /**
12998
     * Reads and process "lp_subscription_settings" setting.
12999
     *
13000
     * @return array
13001
     */
13002
    public static function getSubscriptionSettings()
13003
    {
13004
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
13005
        if (empty($subscriptionSettings)) {
13006
            // By default allow both settings
13007
            $subscriptionSettings = [
13008
                'allow_add_users_to_lp' => true,
13009
                'allow_add_users_to_lp_category' => true,
13010
            ];
13011
        } else {
13012
            $subscriptionSettings = $subscriptionSettings['options'];
13013
        }
13014
13015
        return $subscriptionSettings;
13016
    }
13017
13018
    /**
13019
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
13020
     */
13021
    public function exportToCourseBuildFormat()
13022
    {
13023
        if (!api_is_allowed_to_edit()) {
13024
            return false;
13025
        }
13026
13027
        $courseBuilder = new CourseBuilder();
13028
        $itemList = [];
13029
        /** @var learnpathItem $item */
13030
        foreach ($this->items as $item) {
13031
            $itemList[$item->get_type()][] = $item->get_path();
13032
        }
13033
13034
        if (empty($itemList)) {
13035
            return false;
13036
        }
13037
13038
        if (isset($itemList['document'])) {
13039
            // Get parents
13040
            foreach ($itemList['document'] as $documentId) {
13041
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
13042
                if (!empty($documentInfo['parents'])) {
13043
                    foreach ($documentInfo['parents'] as $parentInfo) {
13044
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
13045
                            continue;
13046
                        }
13047
                        $itemList['document'][] = $parentInfo['iid'];
13048
                    }
13049
                }
13050
            }
13051
13052
            $courseInfo = api_get_course_info();
13053
            foreach ($itemList['document'] as $documentId) {
13054
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
13055
                $items = DocumentManager::get_resources_from_source_html(
13056
                    $documentInfo['absolute_path'],
13057
                    true,
13058
                    TOOL_DOCUMENT
13059
                );
13060
13061
                if (!empty($items)) {
13062
                    foreach ($items as $item) {
13063
                        // Get information about source url
13064
                        $url = $item[0]; // url
13065
                        $scope = $item[1]; // scope (local, remote)
13066
                        $type = $item[2]; // type (rel, abs, url)
13067
13068
                        $origParseUrl = parse_url($url);
13069
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
13070
13071
                        if ($scope == 'local') {
13072
                            if ($type == 'abs' || $type == 'rel') {
13073
                                $documentFile = strstr($realOrigPath, 'document');
13074
                                if (strpos($realOrigPath, $documentFile) !== false) {
13075
                                    $documentFile = str_replace('document', '', $documentFile);
13076
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
13077
                                    // Document found! Add it to the list
13078
                                    if ($itemDocumentId) {
13079
                                        $itemList['document'][] = $itemDocumentId;
13080
                                    }
13081
                                }
13082
                            }
13083
                        }
13084
                    }
13085
                }
13086
            }
13087
13088
            $courseBuilder->build_documents(
13089
                api_get_session_id(),
13090
                $this->get_course_int_id(),
13091
                true,
13092
                $itemList['document']
13093
            );
13094
        }
13095
13096
        if (isset($itemList['quiz'])) {
13097
            $courseBuilder->build_quizzes(
13098
                api_get_session_id(),
13099
                $this->get_course_int_id(),
13100
                true,
13101
                $itemList['quiz']
13102
            );
13103
        }
13104
13105
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
13106
13107
        /*if (!empty($itemList['thread'])) {
13108
            $postList = [];
13109
            foreach ($itemList['thread'] as $postId) {
13110
                $post = get_post_information($postId);
13111
                if ($post) {
13112
                    if (!isset($itemList['forum'])) {
13113
                        $itemList['forum'] = [];
13114
                    }
13115
                    $itemList['forum'][] = $post['forum_id'];
13116
                    $postList[] = $postId;
13117
                }
13118
            }
13119
13120
            if (!empty($postList)) {
13121
                $courseBuilder->build_forum_posts(
13122
                    $this->get_course_int_id(),
13123
                    null,
13124
                    null,
13125
                    $postList
13126
                );
13127
            }
13128
        }*/
13129
13130
        if (!empty($itemList['thread'])) {
13131
            $threadList = [];
13132
            $em = Database::getManager();
13133
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
13134
            foreach ($itemList['thread'] as $threadId) {
13135
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
13136
                $thread = $repo->find($threadId);
13137
                if ($thread) {
13138
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
13139
                    $threadList[] = $thread->getIid();
13140
                }
13141
            }
13142
13143
            if (!empty($threadList)) {
13144
                $courseBuilder->build_forum_topics(
13145
                    api_get_session_id(),
13146
                    $this->get_course_int_id(),
13147
                    null,
13148
                    $threadList
13149
                );
13150
            }
13151
        }
13152
13153
        $forumCategoryList = [];
13154
        if (isset($itemList['forum'])) {
13155
            foreach ($itemList['forum'] as $forumId) {
13156
                $forumInfo = get_forums($forumId);
13157
                $forumCategoryList[] = $forumInfo['forum_category'];
13158
            }
13159
        }
13160
13161
        if (!empty($forumCategoryList)) {
13162
            $courseBuilder->build_forum_category(
13163
                api_get_session_id(),
13164
                $this->get_course_int_id(),
13165
                true,
13166
                $forumCategoryList
13167
            );
13168
        }
13169
13170
        if (!empty($itemList['forum'])) {
13171
            $courseBuilder->build_forums(
13172
                api_get_session_id(),
13173
                $this->get_course_int_id(),
13174
                true,
13175
                $itemList['forum']
13176
            );
13177
        }
13178
13179
        if (isset($itemList['link'])) {
13180
            $courseBuilder->build_links(
13181
                api_get_session_id(),
13182
                $this->get_course_int_id(),
13183
                true,
13184
                $itemList['link']
13185
            );
13186
        }
13187
13188
        if (!empty($itemList['student_publication'])) {
13189
            $courseBuilder->build_works(
13190
                api_get_session_id(),
13191
                $this->get_course_int_id(),
13192
                true,
13193
                $itemList['student_publication']
13194
            );
13195
        }
13196
13197
        $courseBuilder->build_learnpaths(
13198
            api_get_session_id(),
13199
            $this->get_course_int_id(),
13200
            true,
13201
            [$this->get_id()],
13202
            false
13203
        );
13204
13205
        $courseBuilder->restoreDocumentsFromList();
13206
13207
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
13208
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
13209
        $result = DocumentManager::file_send_for_download(
13210
            $zipPath,
13211
            true,
13212
            $this->get_name().'.zip'
13213
        );
13214
13215
        if ($result) {
13216
            api_not_allowed();
13217
        }
13218
13219
        return true;
13220
    }
13221
13222
    /**
13223
     * Get whether this is a learning path with the accumulated work time or not.
13224
     *
13225
     * @return int
13226
     */
13227
    public function getAccumulateWorkTime()
13228
    {
13229
        return (int) $this->accumulateWorkTime;
13230
    }
13231
13232
    /**
13233
     * Get whether this is a learning path with the accumulated work time or not.
13234
     *
13235
     * @return int
13236
     */
13237
    public function getAccumulateWorkTimeTotalCourse()
13238
    {
13239
        $table = Database::get_course_table(TABLE_LP_MAIN);
13240
        $sql = "SELECT SUM(accumulate_work_time) AS total
13241
                FROM $table
13242
                WHERE c_id = ".$this->course_int_id;
13243
        $result = Database::query($sql);
13244
        $row = Database::fetch_array($result);
13245
13246
        return (int) $row['total'];
13247
    }
13248
13249
    /**
13250
     * Set whether this is a learning path with the accumulated work time or not.
13251
     *
13252
     * @param int $value (0 = false, 1 = true)
13253
     *
13254
     * @return bool
13255
     */
13256
    public function setAccumulateWorkTime($value)
13257
    {
13258
        if (!api_get_configuration_value('lp_minimum_time')) {
13259
            return false;
13260
        }
13261
13262
        $this->accumulateWorkTime = (int) $value;
13263
        $table = Database::get_course_table(TABLE_LP_MAIN);
13264
        $lp_id = $this->get_id();
13265
        $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
13266
                WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
13267
        Database::query($sql);
13268
13269
        return true;
13270
    }
13271
13272
    /**
13273
     * @param int $lpId
13274
     * @param int $courseId
13275
     *
13276
     * @return mixed
13277
     */
13278
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
13279
    {
13280
        $lpId = (int) $lpId;
13281
        $courseId = (int) $courseId;
13282
13283
        $table = Database::get_course_table(TABLE_LP_MAIN);
13284
        $sql = "SELECT accumulate_work_time
13285
                FROM $table
13286
                WHERE c_id = $courseId AND id = $lpId";
13287
        $result = Database::query($sql);
13288
        $row = Database::fetch_array($result);
13289
13290
        return $row['accumulate_work_time'];
13291
    }
13292
13293
    /**
13294
     * @param int $courseId
13295
     *
13296
     * @return int
13297
     */
13298
    public static function getAccumulateWorkTimeTotal($courseId)
13299
    {
13300
        $table = Database::get_course_table(TABLE_LP_MAIN);
13301
        $courseId = (int) $courseId;
13302
        $sql = "SELECT SUM(accumulate_work_time) AS total
13303
                FROM $table
13304
                WHERE c_id = $courseId";
13305
        $result = Database::query($sql);
13306
        $row = Database::fetch_array($result);
13307
13308
        return (int) $row['total'];
13309
    }
13310
13311
    /**
13312
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
13313
     * and put the images in.
13314
     *
13315
     * @return array
13316
     */
13317
    public static function getIconSelect()
13318
    {
13319
        $theme = api_get_visual_theme();
13320
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
13321
        $icons = ['' => get_lang('Please select an option')];
13322
13323
        if (is_dir($path)) {
13324
            $finder = new Finder();
13325
            $finder->files()->in($path);
13326
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
13327
            /** @var SplFileInfo $file */
13328
            foreach ($finder as $file) {
13329
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
13330
                    $icons[$file->getFilename()] = $file->getFilename();
13331
                }
13332
            }
13333
        }
13334
13335
        return $icons;
13336
    }
13337
13338
    /**
13339
     * @param int $lpId
13340
     *
13341
     * @return string
13342
     */
13343
    public static function getSelectedIcon($lpId)
13344
    {
13345
        $extraFieldValue = new ExtraFieldValue('lp');
13346
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
13347
        $icon = '';
13348
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
13349
            $icon = $lpIcon['value'];
13350
        }
13351
13352
        return $icon;
13353
    }
13354
13355
    /**
13356
     * @param int $lpId
13357
     *
13358
     * @return string
13359
     */
13360
    public static function getSelectedIconHtml($lpId)
13361
    {
13362
        $icon = self::getSelectedIcon($lpId);
13363
13364
        if (empty($icon)) {
13365
            return '';
13366
        }
13367
13368
        $theme = api_get_visual_theme();
13369
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
13370
13371
        return Display::img($path);
13372
    }
13373
13374
    /**
13375
     * @param string $value
13376
     *
13377
     * @return string
13378
     */
13379
    public function cleanItemTitle($value)
13380
    {
13381
        $value = Security::remove_XSS(strip_tags($value));
13382
13383
        return $value;
13384
    }
13385
13386
    public function setItemTitle(FormValidator $form)
13387
    {
13388
        if (api_get_configuration_value('save_titles_as_html')) {
13389
            $form->addHtmlEditor(
13390
                'title',
13391
                get_lang('Title'),
13392
                true,
13393
                false,
13394
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
13395
            );
13396
        } else {
13397
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
13398
            $form->applyFilter('title', 'trim');
13399
            $form->applyFilter('title', 'html_filter');
13400
        }
13401
    }
13402
13403
    /**
13404
     * @return array
13405
     */
13406
    public function getItemsForForm($addParentCondition = false)
13407
    {
13408
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
13409
        $course_id = api_get_course_int_id();
13410
13411
        $sql = "SELECT * FROM $tbl_lp_item
13412
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
13413
13414
        if ($addParentCondition) {
13415
            $sql .= ' AND parent_item_id = 0 ';
13416
        }
13417
        $sql .= ' ORDER BY display_order ASC';
13418
13419
        $result = Database::query($sql);
13420
        $arrLP = [];
13421
        while ($row = Database::fetch_array($result)) {
13422
            $arrLP[] = [
13423
                'iid' => $row['iid'],
13424
                'id' => $row['iid'],
13425
                'item_type' => $row['item_type'],
13426
                'title' => $this->cleanItemTitle($row['title']),
13427
                'title_raw' => $row['title'],
13428
                'path' => $row['path'],
13429
                'description' => Security::remove_XSS($row['description']),
13430
                'parent_item_id' => $row['parent_item_id'],
13431
                'previous_item_id' => $row['previous_item_id'],
13432
                'next_item_id' => $row['next_item_id'],
13433
                'display_order' => $row['display_order'],
13434
                'max_score' => $row['max_score'],
13435
                'min_score' => $row['min_score'],
13436
                'mastery_score' => $row['mastery_score'],
13437
                'prerequisite' => $row['prerequisite'],
13438
                'max_time_allowed' => $row['max_time_allowed'],
13439
                'prerequisite_min_score' => $row['prerequisite_min_score'],
13440
                'prerequisite_max_score' => $row['prerequisite_max_score'],
13441
            ];
13442
        }
13443
13444
        return $arrLP;
13445
    }
13446
13447
    /**
13448
     * Get the depth level of LP item.
13449
     *
13450
     * @param array $items
13451
     * @param int   $currentItemId
13452
     *
13453
     * @return int
13454
     */
13455
    private static function get_level_for_item($items, $currentItemId)
13456
    {
13457
        $parentItemId = 0;
13458
        if (isset($items[$currentItemId])) {
13459
            $parentItemId = $items[$currentItemId]->parent;
13460
        }
13461
13462
        if ($parentItemId == 0) {
13463
            return 0;
13464
        } else {
13465
            return self::get_level_for_item($items, $parentItemId) + 1;
13466
        }
13467
    }
13468
13469
    /**
13470
     * Generate the link for a learnpath category as course tool.
13471
     *
13472
     * @param int $categoryId
13473
     *
13474
     * @return string
13475
     */
13476
    private static function getCategoryLinkForTool($categoryId)
13477
    {
13478
        $categoryId = (int) $categoryId;
13479
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
13480
            .http_build_query(
13481
                [
13482
                    'action' => 'view_category',
13483
                    'id' => $categoryId,
13484
                ]
13485
            );
13486
13487
        return $link;
13488
    }
13489
13490
    /**
13491
     * Return the scorm item type object with spaces replaced with _
13492
     * The return result is use to build a css classname like scorm_type_$return.
13493
     *
13494
     * @param $in_type
13495
     *
13496
     * @return mixed
13497
     */
13498
    private static function format_scorm_type_item($in_type)
13499
    {
13500
        return str_replace(' ', '_', $in_type);
13501
    }
13502
13503
    /**
13504
     * Check and obtain the lp final item if exist.
13505
     *
13506
     * @return learnpathItem
13507
     */
13508
    private function getFinalItem()
13509
    {
13510
        if (empty($this->items)) {
13511
            return null;
13512
        }
13513
13514
        foreach ($this->items as $item) {
13515
            if ($item->type !== 'final_item') {
13516
                continue;
13517
            }
13518
13519
            return $item;
13520
        }
13521
    }
13522
13523
    /**
13524
     * Get the LP Final Item Template.
13525
     *
13526
     * @return string
13527
     */
13528
    private function getFinalItemTemplate()
13529
    {
13530
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
13531
    }
13532
13533
    /**
13534
     * Get the LP Final Item Url.
13535
     *
13536
     * @return string
13537
     */
13538
    private function getSavedFinalItem()
13539
    {
13540
        $finalItem = $this->getFinalItem();
13541
        $doc = DocumentManager::get_document_data_by_id(
13542
            $finalItem->path,
13543
            $this->cc
13544
        );
13545
        if ($doc && file_exists($doc['absolute_path'])) {
13546
            return file_get_contents($doc['absolute_path']);
13547
        }
13548
13549
        return '';
13550
    }
13551
}
13552