Passed
Push — master ( 54bad5...486a74 )
by Julito
13:01 queued 15s
created

learnpath   F

Complexity

Total Complexity 1816

Size/Duplication

Total Lines 13549
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 7399
c 3
b 0
f 0
dl 0
loc 13549
rs 0.8
wmc 1816

219 Methods

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

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

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

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

    return false;
}

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

Loading history...
4277
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4278
        $lp_id = (int) $lp_id;
4279
        $sql = "SELECT * FROM $tbl_lp
4280
                WHERE iid = $lp_id";
4281
        $result = Database::query($sql);
4282
4283
        if (Database::num_rows($result)) {
4284
            $row = Database::fetch_array($result);
4285
            $name = Database::escape_string($row['name']);
4286
            if ($set_visibility == 'i') {
4287
                $v = 0;
4288
            }
4289
            if ($set_visibility == 'v') {
4290
                $v = 1;
4291
            }
4292
4293
            $session_id = api_get_session_id();
4294
            $session_condition = api_get_session_condition($session_id);
4295
4296
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4297
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4298
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4299
4300
            $sql = "SELECT * FROM $tbl_tool
4301
                    WHERE
4302
                        c_id = $course_id AND
4303
                        (link = '$link' OR link = '$oldLink') AND
4304
                        image = 'scormbuilder.gif' AND
4305
                        (
4306
                            link LIKE '$link%' OR
4307
                            link LIKE '$oldLink%'
4308
                        )
4309
                        $session_condition
4310
                    ";
4311
4312
            $result = Database::query($sql);
4313
            $num = Database::num_rows($result);
4314
            if ($set_visibility == 'i' && $num > 0) {
4315
                $sql = "DELETE FROM $tbl_tool
4316
                        WHERE
4317
                            c_id = $course_id AND
4318
                            (link = '$link' OR link = '$oldLink') AND
4319
                            image='scormbuilder.gif'
4320
                            $session_condition";
4321
                Database::query($sql);
4322
            } elseif ($set_visibility == 'v' && $num == 0) {
4323
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4324
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4325
                Database::query($sql);
4326
                $insertId = Database::insert_id();
4327
                if ($insertId) {
4328
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4329
                    Database::query($sql);
4330
                }
4331
            } elseif ($set_visibility == 'v' && $num > 0) {
4332
                $sql = "UPDATE $tbl_tool SET
4333
                            c_id = $course_id,
4334
                            name = '$name',
4335
                            link = '$link',
4336
                            image = 'scormbuilder.gif',
4337
                            visibility = '$v',
4338
                            admin = '0',
4339
                            address = 'pastillegris.gif',
4340
                            added_tool = 0,
4341
                            session_id = $session_id
4342
                        WHERE
4343
                            c_id = ".$course_id." AND
4344
                            (link = '$link' OR link = '$oldLink') AND
4345
                            image='scormbuilder.gif'
4346
                            $session_condition
4347
                        ";
4348
                Database::query($sql);
4349
            } else {
4350
                // Parameter and database incompatible, do nothing, exit.
4351
                return false;
4352
            }
4353
        } else {
4354
            return false;
4355
        }
4356
    }
4357
4358
    /**
4359
     * Publishes a learnpath.
4360
     * Show or hide the learnpath category on the course homepage.
4361
     *
4362
     * @param int $id
4363
     * @param int $setVisibility
4364
     *
4365
     * @throws \Doctrine\ORM\NonUniqueResultException
4366
     * @throws \Doctrine\ORM\ORMException
4367
     * @throws \Doctrine\ORM\OptimisticLockException
4368
     * @throws \Doctrine\ORM\TransactionRequiredException
4369
     *
4370
     * @return bool
4371
     */
4372
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4373
    {
4374
        $courseId = api_get_course_int_id();
4375
        $sessionId = api_get_session_id();
4376
        $sessionCondition = api_get_session_condition(
4377
            $sessionId,
4378
            true,
4379
            false,
4380
            't.sessionId'
4381
        );
4382
4383
        $em = Database::getManager();
4384
4385
        /** @var CLpCategory $category */
4386
        $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
4387
4388
        if (!$category) {
4389
            return false;
4390
        }
4391
4392
        if (empty($courseId)) {
4393
            return false;
4394
        }
4395
4396
        $link = self::getCategoryLinkForTool($id);
4397
4398
        /** @var CTool $tool */
4399
        $tool = $em->createQuery("
4400
                SELECT t FROM ChamiloCourseBundle:CTool t
4401
                WHERE
4402
                    t.course = :course AND
4403
                    t.link = :link1 AND
4404
                    t.image LIKE 'lp_category.%' AND
4405
                    t.link LIKE :link2
4406
                    $sessionCondition
4407
            ")
4408
            ->setParameters([
4409
                'course' => $courseId,
4410
                'link1' => $link,
4411
                'link2' => "$link%",
4412
            ])
4413
            ->getOneOrNullResult();
4414
4415
        if ($setVisibility == 0 && $tool) {
4416
            $em->remove($tool);
4417
            $em->flush();
4418
4419
            return true;
4420
        }
4421
4422
        if ($setVisibility == 1 && !$tool) {
4423
            $tool = new CTool();
4424
            $tool
4425
                ->setCategory('authoring')
4426
                ->setCourse(api_get_course_entity($courseId))
4427
                ->setName(strip_tags($category->getName()))
4428
                ->setLink($link)
4429
                ->setImage('lp_category.png')
4430
                ->setVisibility(1)
4431
                ->setAdmin(0)
4432
                ->setAddress('pastillegris.gif')
4433
                ->setAddedTool(0)
4434
                ->setSessionId($sessionId)
4435
                ->setTarget('_self');
4436
4437
            $em->persist($tool);
4438
            $em->flush();
4439
4440
            $tool->setId($tool->getIid());
4441
4442
            $em->persist($tool);
4443
            $em->flush();
4444
4445
            return true;
4446
        }
4447
4448
        if ($setVisibility == 1 && $tool) {
4449
            $tool
4450
                ->setName(strip_tags($category->getName()))
4451
                ->setVisibility(1);
4452
4453
            $em->persist($tool);
4454
            $em->flush();
4455
4456
            return true;
4457
        }
4458
4459
        return false;
4460
    }
4461
4462
    /**
4463
     * Check if the learnpath category is visible for a user.
4464
     *
4465
     * @param int
4466
     * @param int
4467
     *
4468
     * @return bool
4469
     */
4470
    public static function categoryIsVisibleForStudent(
4471
        CLpCategory $category,
4472
        User $user,
4473
        $courseId = 0,
4474
        $sessionId = 0
4475
    ) {
4476
        if (empty($category)) {
4477
            return false;
4478
        }
4479
4480
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4481
4482
        if ($isAllowedToEdit) {
4483
            return true;
4484
        }
4485
4486
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4487
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4488
4489
        $courseInfo = api_get_course_info_by_id($courseId);
4490
4491
        $categoryVisibility = api_get_item_visibility(
4492
            $courseInfo,
4493
            TOOL_LEARNPATH_CATEGORY,
4494
            $category->getId(),
4495
            $sessionId
4496
        );
4497
4498
        if ($categoryVisibility !== 1 && $categoryVisibility != -1) {
4499
            return false;
4500
        }
4501
4502
        $subscriptionSettings = self::getSubscriptionSettings();
4503
4504
        if ($subscriptionSettings['allow_add_users_to_lp_category'] == false) {
4505
            return true;
4506
        }
4507
4508
        $users = $category->getUsers();
4509
4510
        if (empty($users) || !$users->count()) {
4511
            return true;
4512
        }
4513
4514
        if ($category->hasUserAdded($user)) {
4515
            return true;
4516
        }
4517
4518
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4519
        if (!empty($groups)) {
4520
            $em = Database::getManager();
4521
4522
            /** @var ItemPropertyRepository $itemRepo */
4523
            $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4524
4525
            /** @var CourseRepository $courseRepo */
4526
            $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4527
            $session = null;
4528
            if (!empty($sessionId)) {
4529
                $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4530
            }
4531
4532
            $course = $courseRepo->find($courseId);
4533
4534
            // Subscribed groups to a LP
4535
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4536
                TOOL_LEARNPATH_CATEGORY,
4537
                $category->getId(),
4538
                $course,
4539
                $session
4540
            );
4541
4542
            if (!empty($subscribedGroupsInLp)) {
4543
                $groups = array_column($groups, 'iid');
4544
                /** @var CItemProperty $item */
4545
                foreach ($subscribedGroupsInLp as $item) {
4546
                    if ($item->getGroup() &&
4547
                        in_array($item->getGroup()->getId(), $groups)
4548
                    ) {
4549
                        return true;
4550
                    }
4551
                }
4552
            }
4553
        }
4554
4555
        return false;
4556
    }
4557
4558
    /**
4559
     * Check if a learnpath category is published as course tool.
4560
     *
4561
     * @param int $courseId
4562
     *
4563
     * @return bool
4564
     */
4565
    public static function categoryIsPublished(CLpCategory $category, $courseId)
4566
    {
4567
        return false;
4568
        $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...
4569
        $em = Database::getManager();
4570
4571
        $tools = $em
4572
            ->createQuery("
4573
                SELECT t FROM ChamiloCourseBundle:CTool t
4574
                WHERE t.course = :course AND
4575
                    t.name = :name AND
4576
                    t.image LIKE 'lp_category.%' AND
4577
                    t.link LIKE :link
4578
            ")
4579
            ->setParameters([
4580
                'course' => $courseId,
4581
                'name' => strip_tags($category->getName()),
4582
                'link' => "$link%",
4583
            ])
4584
            ->getResult();
4585
4586
        /** @var CTool $tool */
4587
        $tool = current($tools);
4588
4589
        return $tool ? $tool->getVisibility() : false;
4590
    }
4591
4592
    /**
4593
     * Restart the whole learnpath. Return the URL of the first element.
4594
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4595
     * To use a similar method  statically, use the create_new_attempt() method.
4596
     *
4597
     * @return bool
4598
     */
4599
    public function restart()
4600
    {
4601
        if ($this->debug > 0) {
4602
            error_log('In learnpath::restart()', 0);
4603
        }
4604
        // TODO
4605
        // Call autosave method to save the current progress.
4606
        //$this->index = 0;
4607
        if (api_is_invitee()) {
4608
            return false;
4609
        }
4610
        $session_id = api_get_session_id();
4611
        $course_id = api_get_course_int_id();
4612
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4613
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4614
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4615
        if ($this->debug > 2) {
4616
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4617
        }
4618
        Database::query($sql);
4619
        $view_id = Database::insert_id();
4620
4621
        if ($view_id) {
4622
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4623
            Database::query($sql);
4624
            $this->lp_view_id = $view_id;
4625
            $this->attempt = $this->attempt + 1;
4626
        } else {
4627
            $this->error = 'Could not insert into item_view table...';
4628
4629
            return false;
4630
        }
4631
        $this->autocomplete_parents($this->current);
4632
        foreach ($this->items as $index => $dummy) {
4633
            $this->items[$index]->restart();
4634
            $this->items[$index]->set_lp_view($this->lp_view_id);
4635
        }
4636
        $this->first();
4637
4638
        return true;
4639
    }
4640
4641
    /**
4642
     * Saves the current item.
4643
     *
4644
     * @return bool
4645
     */
4646
    public function save_current()
4647
    {
4648
        $debug = $this->debug;
4649
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4650
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4651
        if ($debug) {
4652
            error_log('save_current() saving item '.$this->current, 0);
4653
            error_log(''.print_r($this->items, true), 0);
4654
        }
4655
        if (isset($this->items[$this->current]) &&
4656
            is_object($this->items[$this->current])
4657
        ) {
4658
            if ($debug) {
4659
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4660
            }
4661
4662
            $res = $this->items[$this->current]->save(
4663
                false,
4664
                $this->prerequisites_match($this->current)
4665
            );
4666
            $this->autocomplete_parents($this->current);
4667
            $status = $this->items[$this->current]->get_status();
4668
            $this->update_queue[$this->current] = $status;
4669
4670
            if ($debug) {
4671
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4672
            }
4673
4674
            return $res;
4675
        }
4676
4677
        return false;
4678
    }
4679
4680
    /**
4681
     * Saves the given item.
4682
     *
4683
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4684
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4685
     *
4686
     * @return bool
4687
     */
4688
    public function save_item($item_id = null, $from_outside = true)
4689
    {
4690
        $debug = $this->debug;
4691
        if ($debug) {
4692
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4693
        }
4694
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4695
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4696
        if (empty($item_id)) {
4697
            $item_id = (int) $_REQUEST['id'];
4698
        }
4699
4700
        if (empty($item_id)) {
4701
            $item_id = $this->get_current_item_id();
4702
        }
4703
        if (isset($this->items[$item_id]) &&
4704
            is_object($this->items[$item_id])
4705
        ) {
4706
            if ($debug) {
4707
                error_log('Object exists');
4708
            }
4709
4710
            // Saving the item.
4711
            $res = $this->items[$item_id]->save(
4712
                $from_outside,
4713
                $this->prerequisites_match($item_id)
4714
            );
4715
4716
            if ($debug) {
4717
                error_log('update_queue before:');
4718
                error_log(print_r($this->update_queue, 1));
4719
            }
4720
            $this->autocomplete_parents($item_id);
4721
4722
            $status = $this->items[$item_id]->get_status();
4723
            $this->update_queue[$item_id] = $status;
4724
4725
            if ($debug) {
4726
                error_log('get_status(): '.$status);
4727
                error_log('update_queue after:');
4728
                error_log(print_r($this->update_queue, 1));
4729
            }
4730
4731
            return $res;
4732
        }
4733
4734
        return false;
4735
    }
4736
4737
    /**
4738
     * Saves the last item seen's ID only in case.
4739
     */
4740
    public function save_last()
4741
    {
4742
        $course_id = api_get_course_int_id();
4743
        $debug = $this->debug;
4744
        if ($debug) {
4745
            error_log('In learnpath::save_last()', 0);
4746
        }
4747
        $session_condition = api_get_session_condition(
4748
            api_get_session_id(),
4749
            true,
4750
            false
4751
        );
4752
        $table = Database::get_course_table(TABLE_LP_VIEW);
4753
4754
        if (isset($this->current) && !api_is_invitee()) {
4755
            if ($debug) {
4756
                error_log('Saving current item ('.$this->current.') for later review', 0);
4757
            }
4758
            $sql = "UPDATE $table SET
4759
                        last_item = ".$this->get_current_item_id()."
4760
                    WHERE
4761
                        c_id = $course_id AND
4762
                        lp_id = ".$this->get_id()." AND
4763
                        user_id = ".$this->get_user_id()." ".$session_condition;
4764
4765
            if ($debug) {
4766
                error_log('Saving last item seen : '.$sql, 0);
4767
            }
4768
            Database::query($sql);
4769
        }
4770
4771
        if (!api_is_invitee()) {
4772
            // Save progress.
4773
            list($progress) = $this->get_progress_bar_text('%');
4774
            if ($progress >= 0 && $progress <= 100) {
4775
                $progress = (int) $progress;
4776
                $sql = "UPDATE $table SET
4777
                            progress = $progress
4778
                        WHERE
4779
                            c_id = $course_id AND
4780
                            lp_id = ".$this->get_id()." AND
4781
                            user_id = ".$this->get_user_id()." ".$session_condition;
4782
                // Ignore errors as some tables might not have the progress field just yet.
4783
                Database::query($sql);
4784
                $this->progress_db = $progress;
4785
            }
4786
        }
4787
    }
4788
4789
    /**
4790
     * Sets the current item ID (checks if valid and authorized first).
4791
     *
4792
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
4793
     */
4794
    public function set_current_item($item_id = null)
4795
    {
4796
        $debug = $this->debug;
4797
        if ($debug) {
4798
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
4799
        }
4800
        if (empty($item_id)) {
4801
            if ($debug) {
4802
                error_log('No new current item given, ignore...', 0);
4803
            }
4804
            // Do nothing.
4805
        } else {
4806
            if ($debug) {
4807
                error_log('New current item given is '.$item_id.'...', 0);
4808
            }
4809
            if (is_numeric($item_id)) {
4810
                $item_id = (int) $item_id;
4811
                // TODO: Check in database here.
4812
                $this->last = $this->current;
4813
                $this->current = $item_id;
4814
                // TODO: Update $this->index as well.
4815
                foreach ($this->ordered_items as $index => $item) {
4816
                    if ($item == $this->current) {
4817
                        $this->index = $index;
4818
                        break;
4819
                    }
4820
                }
4821
                if ($debug) {
4822
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
4823
                }
4824
            } else {
4825
                if ($debug) {
4826
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
4827
                }
4828
            }
4829
        }
4830
    }
4831
4832
    /**
4833
     * Sets the encoding.
4834
     *
4835
     * @param string $enc New encoding
4836
     *
4837
     * @return bool
4838
     *
4839
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
4840
     */
4841
    public function set_encoding($enc = 'UTF-8')
4842
    {
4843
        $enc = api_refine_encoding_id($enc);
4844
        if (empty($enc)) {
4845
            $enc = api_get_system_encoding();
4846
        }
4847
        if (api_is_encoding_supported($enc)) {
4848
            $lp = $this->get_id();
4849
            if ($lp != 0) {
4850
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4851
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
4852
                        WHERE iid = ".$lp;
4853
                $res = Database::query($sql);
4854
4855
                return $res;
4856
            }
4857
        }
4858
4859
        return false;
4860
    }
4861
4862
    /**
4863
     * Sets the JS lib setting in the database directly.
4864
     * This is the JavaScript library file this lp needs to load on startup.
4865
     *
4866
     * @param string $lib Proximity setting
4867
     *
4868
     * @return bool True on update success. False otherwise.
4869
     */
4870
    public function set_jslib($lib = '')
4871
    {
4872
        $lp = $this->get_id();
4873
4874
        if ($lp != 0) {
4875
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4876
            $lib = Database::escape_string($lib);
4877
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
4878
                    WHERE iid = $lp";
4879
            $res = Database::query($sql);
4880
4881
            return $res;
4882
        }
4883
4884
        return false;
4885
    }
4886
4887
    /**
4888
     * Sets the name of the LP maker (publisher) (and save).
4889
     *
4890
     * @param string $name Optional string giving the new content_maker of this learnpath
4891
     *
4892
     * @return bool True
4893
     */
4894
    public function set_maker($name = '')
4895
    {
4896
        if (empty($name)) {
4897
            return false;
4898
        }
4899
        $this->maker = $name;
4900
        $table = Database::get_course_table(TABLE_LP_MAIN);
4901
        $lp_id = $this->get_id();
4902
        $sql = "UPDATE $table SET
4903
                content_maker = '".Database::escape_string($this->maker)."'
4904
                WHERE iid = $lp_id";
4905
        Database::query($sql);
4906
4907
        return true;
4908
    }
4909
4910
    /**
4911
     * Sets the name of the current learnpath (and save).
4912
     *
4913
     * @param string $name Optional string giving the new name of this learnpath
4914
     *
4915
     * @return bool True/False
4916
     */
4917
    public function set_name($name = null)
4918
    {
4919
        if (empty($name)) {
4920
            return false;
4921
        }
4922
        $this->name = $name;
4923
4924
        $lp_id = $this->get_id();
4925
4926
        $repo = Container::getLpRepository();
4927
        /** @var CLp $lp */
4928
        $lp = $repo->find($lp_id);
4929
        $lp->setName($name);
4930
        $repo->updateNodeForResource($lp);
4931
4932
        /*
4933
        $course_id = $this->course_info['real_id'];
4934
        $sql = "UPDATE $lp_table SET
4935
            name = '$name'
4936
            WHERE iid = $lp_id";
4937
        $result = Database::query($sql);
4938
        // If the lp is visible on the homepage, change his name there.
4939
        if (Database::affected_rows($result)) {
4940
        $session_id = api_get_session_id();
4941
        $session_condition = api_get_session_condition($session_id);
4942
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4943
        $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4944
        $sql = "UPDATE $tbl_tool SET name = '$name'
4945
        	    WHERE
4946
        	        c_id = $course_id AND
4947
        	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
4948
        Database::query($sql);*/
4949
4950
        //return true;
4951
        //}
4952
4953
        return false;
4954
    }
4955
4956
    /**
4957
     * Set index specified prefix terms for all items in this path.
4958
     *
4959
     * @param string $terms_string Comma-separated list of terms
4960
     * @param string $prefix       Xapian term prefix
4961
     *
4962
     * @return bool False on error, true otherwise
4963
     */
4964
    public function set_terms_by_prefix($terms_string, $prefix)
4965
    {
4966
        $course_id = api_get_course_int_id();
4967
        if (api_get_setting('search_enabled') !== 'true') {
4968
            return false;
4969
        }
4970
4971
        if (!extension_loaded('xapian')) {
4972
            return false;
4973
        }
4974
4975
        $terms_string = trim($terms_string);
4976
        $terms = explode(',', $terms_string);
4977
        array_walk($terms, 'trim_value');
4978
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
4979
4980
        // Don't do anything if no change, verify only at DB, not the search engine.
4981
        if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
4982
            return false;
4983
        }
4984
4985
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
4986
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
4987
4988
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
4989
        // TODO: Make query secure agains XSS : use member attr instead of post var.
4990
        $lp_id = (int) $_POST['lp_id'];
4991
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
4992
        $result = Database::query($sql);
4993
        $di = new ChamiloIndexer();
4994
4995
        while ($lp_item = Database::fetch_array($result)) {
4996
            // Get search_did.
4997
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
4998
            $sql = 'SELECT * FROM %s
4999
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5000
                    LIMIT 1';
5001
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5002
5003
            //echo $sql; echo '<br>';
5004
            $res = Database::query($sql);
5005
            if (Database::num_rows($res) > 0) {
5006
                $se_ref = Database::fetch_array($res);
5007
                // Compare terms.
5008
                $doc = $di->get_document($se_ref['search_did']);
5009
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5010
                $xterms = [];
5011
                foreach ($xapian_terms as $xapian_term) {
5012
                    $xterms[] = substr($xapian_term['name'], 1);
5013
                }
5014
5015
                $dterms = $terms;
5016
                $missing_terms = array_diff($dterms, $xterms);
5017
                $deprecated_terms = array_diff($xterms, $dterms);
5018
5019
                // Save it to search engine.
5020
                foreach ($missing_terms as $term) {
5021
                    $doc->add_term($prefix.$term, 1);
5022
                }
5023
                foreach ($deprecated_terms as $term) {
5024
                    $doc->remove_term($prefix.$term);
5025
                }
5026
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5027
                $di->getDb()->flush();
5028
            }
5029
        }
5030
5031
        return true;
5032
    }
5033
5034
    /**
5035
     * Sets the theme of the LP (local/remote) (and save).
5036
     *
5037
     * @param string $name Optional string giving the new theme of this learnpath
5038
     *
5039
     * @return bool Returns true if theme name is not empty
5040
     */
5041
    public function set_theme($name = '')
5042
    {
5043
        $this->theme = $name;
5044
        $table = Database::get_course_table(TABLE_LP_MAIN);
5045
        $lp_id = $this->get_id();
5046
        $sql = "UPDATE $table
5047
                SET theme = '".Database::escape_string($this->theme)."'
5048
                WHERE iid = $lp_id";
5049
        Database::query($sql);
5050
5051
        return true;
5052
    }
5053
5054
    /**
5055
     * Sets the image of an LP (and save).
5056
     *
5057
     * @param string $name Optional string giving the new image of this learnpath
5058
     *
5059
     * @return bool Returns true if theme name is not empty
5060
     */
5061
    public function set_preview_image($name = '')
5062
    {
5063
        $this->preview_image = $name;
5064
        $table = Database::get_course_table(TABLE_LP_MAIN);
5065
        $lp_id = $this->get_id();
5066
        $sql = "UPDATE $table SET
5067
                preview_image = '".Database::escape_string($this->preview_image)."'
5068
                WHERE iid = $lp_id";
5069
        Database::query($sql);
5070
5071
        return true;
5072
    }
5073
5074
    /**
5075
     * Sets the author of a LP (and save).
5076
     *
5077
     * @param string $name Optional string giving the new author of this learnpath
5078
     *
5079
     * @return bool Returns true if author's name is not empty
5080
     */
5081
    public function set_author($name = '')
5082
    {
5083
        $this->author = $name;
5084
        $table = Database::get_course_table(TABLE_LP_MAIN);
5085
        $lp_id = $this->get_id();
5086
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5087
                WHERE iid = $lp_id";
5088
        Database::query($sql);
5089
5090
        return true;
5091
    }
5092
5093
    /**
5094
     * Sets the hide_toc_frame parameter of a LP (and save).
5095
     *
5096
     * @param int $hide 1 if frame is hidden 0 then else
5097
     *
5098
     * @return bool Returns true if author's name is not empty
5099
     */
5100
    public function set_hide_toc_frame($hide)
5101
    {
5102
        if (intval($hide) == $hide) {
5103
            $this->hide_toc_frame = $hide;
5104
            $table = Database::get_course_table(TABLE_LP_MAIN);
5105
            $lp_id = $this->get_id();
5106
            $sql = "UPDATE $table SET
5107
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5108
                    WHERE iid = $lp_id";
5109
            Database::query($sql);
5110
5111
            return true;
5112
        }
5113
5114
        return false;
5115
    }
5116
5117
    /**
5118
     * Sets the prerequisite of a LP (and save).
5119
     *
5120
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5121
     *
5122
     * @return bool returns true if prerequisite is not empty
5123
     */
5124
    public function set_prerequisite($prerequisite)
5125
    {
5126
        $this->prerequisite = (int) $prerequisite;
5127
        $table = Database::get_course_table(TABLE_LP_MAIN);
5128
        $lp_id = $this->get_id();
5129
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5130
                WHERE iid = $lp_id";
5131
        Database::query($sql);
5132
5133
        return true;
5134
    }
5135
5136
    /**
5137
     * Sets the location/proximity of the LP (local/remote) (and save).
5138
     *
5139
     * @param string $name Optional string giving the new location of this learnpath
5140
     *
5141
     * @return bool True on success / False on error
5142
     */
5143
    public function set_proximity($name = '')
5144
    {
5145
        if (empty($name)) {
5146
            return false;
5147
        }
5148
5149
        $this->proximity = $name;
5150
        $table = Database::get_course_table(TABLE_LP_MAIN);
5151
        $lp_id = $this->get_id();
5152
        $sql = "UPDATE $table SET
5153
                    content_local = '".Database::escape_string($name)."'
5154
                WHERE iid = $lp_id";
5155
        Database::query($sql);
5156
5157
        return true;
5158
    }
5159
5160
    /**
5161
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5162
     *
5163
     * @param int $id DB ID of the item
5164
     */
5165
    public function set_previous_item($id)
5166
    {
5167
        if ($this->debug > 0) {
5168
            error_log('In learnpath::set_previous_item()', 0);
5169
        }
5170
        $this->last = $id;
5171
    }
5172
5173
    /**
5174
     * Sets use_max_score.
5175
     *
5176
     * @param int $use_max_score Optional string giving the new location of this learnpath
5177
     *
5178
     * @return bool True on success / False on error
5179
     */
5180
    public function set_use_max_score($use_max_score = 1)
5181
    {
5182
        $use_max_score = (int) $use_max_score;
5183
        $this->use_max_score = $use_max_score;
5184
        $table = Database::get_course_table(TABLE_LP_MAIN);
5185
        $lp_id = $this->get_id();
5186
        $sql = "UPDATE $table SET
5187
                    use_max_score = '".$this->use_max_score."'
5188
                WHERE iid = $lp_id";
5189
        Database::query($sql);
5190
5191
        return true;
5192
    }
5193
5194
    /**
5195
     * Sets and saves the expired_on date.
5196
     *
5197
     * @param string $expired_on Optional string giving the new author of this learnpath
5198
     *
5199
     * @throws \Doctrine\ORM\OptimisticLockException
5200
     *
5201
     * @return bool Returns true if author's name is not empty
5202
     */
5203
    public function set_expired_on($expired_on)
5204
    {
5205
        $em = Database::getManager();
5206
        /** @var CLp $lp */
5207
        $lp = $em
5208
            ->getRepository('ChamiloCourseBundle:CLp')
5209
            ->findOneBy(
5210
                [
5211
                    'iid' => $this->get_id(),
5212
                ]
5213
            );
5214
5215
        if (!$lp) {
5216
            return false;
5217
        }
5218
5219
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5220
5221
        $lp->setExpiredOn($this->expired_on);
5222
        $em->persist($lp);
5223
        $em->flush();
5224
5225
        return true;
5226
    }
5227
5228
    /**
5229
     * Sets and saves the publicated_on date.
5230
     *
5231
     * @param string $publicated_on Optional string giving the new author of this learnpath
5232
     *
5233
     * @throws \Doctrine\ORM\OptimisticLockException
5234
     *
5235
     * @return bool Returns true if author's name is not empty
5236
     */
5237
    public function set_publicated_on($publicated_on)
5238
    {
5239
        $em = Database::getManager();
5240
        /** @var CLp $lp */
5241
        $lp = $em
5242
            ->getRepository('ChamiloCourseBundle:CLp')
5243
            ->findOneBy(
5244
                [
5245
                    'iid' => $this->get_id(),
5246
                ]
5247
            );
5248
5249
        if (!$lp) {
5250
            return false;
5251
        }
5252
5253
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5254
        $lp->setPublicatedOn($this->publicated_on);
5255
        $em->persist($lp);
5256
        $em->flush();
5257
5258
        return true;
5259
    }
5260
5261
    /**
5262
     * Sets and saves the expired_on date.
5263
     *
5264
     * @return bool Returns true if author's name is not empty
5265
     */
5266
    public function set_modified_on()
5267
    {
5268
        $this->modified_on = api_get_utc_datetime();
5269
        $table = Database::get_course_table(TABLE_LP_MAIN);
5270
        $lp_id = $this->get_id();
5271
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5272
                WHERE iid = $lp_id";
5273
        Database::query($sql);
5274
5275
        return true;
5276
    }
5277
5278
    /**
5279
     * Sets the object's error message.
5280
     *
5281
     * @param string $error Error message. If empty, reinits the error string
5282
     */
5283
    public function set_error_msg($error = '')
5284
    {
5285
        if ($this->debug > 0) {
5286
            error_log('In learnpath::set_error_msg()', 0);
5287
        }
5288
        if (empty($error)) {
5289
            $this->error = '';
5290
        } else {
5291
            $this->error .= $error;
5292
        }
5293
    }
5294
5295
    /**
5296
     * Launches the current item if not 'sco'
5297
     * (starts timer and make sure there is a record ready in the DB).
5298
     *
5299
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5300
     *
5301
     * @return bool
5302
     */
5303
    public function start_current_item($allow_new_attempt = false)
5304
    {
5305
        $debug = $this->debug;
5306
        if ($debug) {
5307
            error_log('In learnpath::start_current_item()');
5308
            error_log('current: '.$this->current);
5309
        }
5310
        if ($this->current != 0 && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5311
            $type = $this->get_type();
5312
            $item_type = $this->items[$this->current]->get_type();
5313
            if (($type == 2 && $item_type != 'sco') ||
5314
                ($type == 3 && $item_type != 'au') ||
5315
                ($type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES)
5316
            ) {
5317
                if ($debug) {
5318
                    error_log('item type: '.$item_type);
5319
                    error_log('lp type: '.$type);
5320
                }
5321
                $this->items[$this->current]->open($allow_new_attempt);
5322
                $this->autocomplete_parents($this->current);
5323
                $prereq_check = $this->prerequisites_match($this->current);
5324
                if ($debug) {
5325
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5326
                }
5327
                $this->items[$this->current]->save(false, $prereq_check);
5328
            }
5329
            // If sco, then it is supposed to have been updated by some other call.
5330
            if ($item_type == 'sco') {
5331
                $this->items[$this->current]->restart();
5332
            }
5333
        }
5334
        if ($debug) {
5335
            error_log('lp_view_session_id');
5336
            error_log($this->lp_view_session_id);
5337
            error_log('api session id');
5338
            error_log(api_get_session_id());
5339
            error_log('End of learnpath::start_current_item()');
5340
        }
5341
5342
        return true;
5343
    }
5344
5345
    /**
5346
     * Stops the processing and counters for the old item (as held in $this->last).
5347
     *
5348
     * @return bool True/False
5349
     */
5350
    public function stop_previous_item()
5351
    {
5352
        $debug = $this->debug;
5353
        if ($debug) {
5354
            error_log('In learnpath::stop_previous_item()', 0);
5355
        }
5356
5357
        if ($this->last != 0 && $this->last != $this->current &&
5358
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5359
        ) {
5360
            if ($debug) {
5361
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5362
            }
5363
            switch ($this->get_type()) {
5364
                case '3':
5365
                    if ($this->items[$this->last]->get_type() != 'au') {
5366
                        if ($debug) {
5367
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5368
                        }
5369
                        $this->items[$this->last]->close();
5370
                    } else {
5371
                        if ($debug) {
5372
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5373
                        }
5374
                    }
5375
                    break;
5376
                case '2':
5377
                    if ($this->items[$this->last]->get_type() != 'sco') {
5378
                        if ($debug) {
5379
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5380
                        }
5381
                        $this->items[$this->last]->close();
5382
                    } else {
5383
                        if ($debug) {
5384
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5385
                        }
5386
                    }
5387
                    break;
5388
                case '1':
5389
                default:
5390
                    if ($debug) {
5391
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5392
                    }
5393
                    $this->items[$this->last]->close();
5394
                    break;
5395
            }
5396
        } else {
5397
            if ($debug) {
5398
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5399
            }
5400
5401
            return false;
5402
        }
5403
5404
        return true;
5405
    }
5406
5407
    /**
5408
     * Updates the default view mode from fullscreen to embedded and inversely.
5409
     *
5410
     * @return string The current default view mode ('fullscreen' or 'embedded')
5411
     */
5412
    public function update_default_view_mode()
5413
    {
5414
        $table = Database::get_course_table(TABLE_LP_MAIN);
5415
        $sql = "SELECT * FROM $table
5416
                WHERE iid = ".$this->get_id();
5417
        $res = Database::query($sql);
5418
        if (Database::num_rows($res) > 0) {
5419
            $row = Database::fetch_array($res);
5420
            $default_view_mode = $row['default_view_mod'];
5421
            $view_mode = $default_view_mode;
5422
            switch ($default_view_mode) {
5423
                case 'fullscreen': // default with popup
5424
                    $view_mode = 'embedded';
5425
                    break;
5426
                case 'embedded': // default view with left menu
5427
                    $view_mode = 'embedframe';
5428
                    break;
5429
                case 'embedframe': //folded menu
5430
                    $view_mode = 'impress';
5431
                    break;
5432
                case 'impress':
5433
                    $view_mode = 'fullscreen';
5434
                    break;
5435
            }
5436
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5437
                    WHERE iid = ".$this->get_id();
5438
            Database::query($sql);
5439
            $this->mode = $view_mode;
5440
5441
            return $view_mode;
5442
        }
5443
5444
        return -1;
5445
    }
5446
5447
    /**
5448
     * Updates the default behaviour about auto-commiting SCORM updates.
5449
     *
5450
     * @return bool True if auto-commit has been set to 'on', false otherwise
5451
     */
5452
    public function update_default_scorm_commit()
5453
    {
5454
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5455
        $sql = "SELECT * FROM $lp_table
5456
                WHERE iid = ".$this->get_id();
5457
        $res = Database::query($sql);
5458
        if (Database::num_rows($res) > 0) {
5459
            $row = Database::fetch_array($res);
5460
            $force = $row['force_commit'];
5461
            if ($force == 1) {
5462
                $force = 0;
5463
                $force_return = false;
5464
            } elseif ($force == 0) {
5465
                $force = 1;
5466
                $force_return = true;
5467
            }
5468
            $sql = "UPDATE $lp_table SET force_commit = $force
5469
                    WHERE iid = ".$this->get_id();
5470
            Database::query($sql);
5471
            $this->force_commit = $force_return;
5472
5473
            return $force_return;
5474
        }
5475
5476
        return -1;
5477
    }
5478
5479
    /**
5480
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5481
     *
5482
     * @return bool True on success, false on failure
5483
     */
5484
    public function update_display_order()
5485
    {
5486
        $course_id = api_get_course_int_id();
5487
        $table = Database::get_course_table(TABLE_LP_MAIN);
5488
        $sql = "SELECT * FROM $table
5489
                WHERE c_id = $course_id
5490
                ORDER BY display_order";
5491
        $res = Database::query($sql);
5492
        if ($res === false) {
5493
            return false;
5494
        }
5495
5496
        $num = Database::num_rows($res);
5497
        // First check the order is correct, globally (might be wrong because
5498
        // of versions < 1.8.4).
5499
        if ($num > 0) {
5500
            $i = 1;
5501
            while ($row = Database::fetch_array($res)) {
5502
                if ($row['display_order'] != $i) {
5503
                    // If we find a gap in the order, we need to fix it.
5504
                    $sql = "UPDATE $table SET display_order = $i
5505
                            WHERE iid = ".$row['iid'];
5506
                    Database::query($sql);
5507
                }
5508
                $i++;
5509
            }
5510
        }
5511
5512
        return true;
5513
    }
5514
5515
    /**
5516
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5517
     *
5518
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5519
     */
5520
    public function update_reinit()
5521
    {
5522
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5523
        $sql = "SELECT * FROM $lp_table
5524
                WHERE iid = ".$this->get_id();
5525
        $res = Database::query($sql);
5526
        if (Database::num_rows($res) > 0) {
5527
            $row = Database::fetch_array($res);
5528
            $force = $row['prevent_reinit'];
5529
            if ($force == 1) {
5530
                $force = 0;
5531
            } elseif ($force == 0) {
5532
                $force = 1;
5533
            }
5534
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5535
                    WHERE iid = ".$this->get_id();
5536
            Database::query($sql);
5537
            $this->prevent_reinit = $force;
5538
5539
            return $force;
5540
        }
5541
5542
        return -1;
5543
    }
5544
5545
    /**
5546
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5547
     *
5548
     * @return string 'single', 'multi' or 'seriousgame'
5549
     *
5550
     * @author ndiechburg <[email protected]>
5551
     */
5552
    public function get_attempt_mode()
5553
    {
5554
        //Set default value for seriousgame_mode
5555
        if (!isset($this->seriousgame_mode)) {
5556
            $this->seriousgame_mode = 0;
5557
        }
5558
        // Set default value for prevent_reinit
5559
        if (!isset($this->prevent_reinit)) {
5560
            $this->prevent_reinit = 1;
5561
        }
5562
        if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
5563
            return 'seriousgame';
5564
        }
5565
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
5566
            return 'single';
5567
        }
5568
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
5569
            return 'multiple';
5570
        }
5571
5572
        return 'single';
5573
    }
5574
5575
    /**
5576
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5577
     *
5578
     * @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...
5579
     *
5580
     * @return bool
5581
     *
5582
     * @author ndiechburg <[email protected]>
5583
     */
5584
    public function set_attempt_mode($mode)
5585
    {
5586
        switch ($mode) {
5587
            case 'seriousgame':
5588
                $sg_mode = 1;
5589
                $prevent_reinit = 1;
5590
                break;
5591
            case 'single':
5592
                $sg_mode = 0;
5593
                $prevent_reinit = 1;
5594
                break;
5595
            case 'multiple':
5596
                $sg_mode = 0;
5597
                $prevent_reinit = 0;
5598
                break;
5599
            default:
5600
                $sg_mode = 0;
5601
                $prevent_reinit = 0;
5602
                break;
5603
        }
5604
        $this->prevent_reinit = $prevent_reinit;
5605
        $this->seriousgame_mode = $sg_mode;
5606
        $table = Database::get_course_table(TABLE_LP_MAIN);
5607
        $sql = "UPDATE $table SET
5608
                prevent_reinit = $prevent_reinit ,
5609
                seriousgame_mode = $sg_mode
5610
                WHERE iid = ".$this->get_id();
5611
        $res = Database::query($sql);
5612
        if ($res) {
5613
            return true;
5614
        } else {
5615
            return false;
5616
        }
5617
    }
5618
5619
    /**
5620
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5621
     *
5622
     * @author ndiechburg <[email protected]>
5623
     */
5624
    public function switch_attempt_mode()
5625
    {
5626
        $mode = $this->get_attempt_mode();
5627
        switch ($mode) {
5628
            case 'single':
5629
                $next_mode = 'multiple';
5630
                break;
5631
            case 'multiple':
5632
                $next_mode = 'seriousgame';
5633
                break;
5634
            case 'seriousgame':
5635
            default:
5636
                $next_mode = 'single';
5637
                break;
5638
        }
5639
        $this->set_attempt_mode($next_mode);
5640
    }
5641
5642
    /**
5643
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5644
     * but possibility to do again a completed item.
5645
     *
5646
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5647
     *
5648
     * @author ndiechburg <[email protected]>
5649
     */
5650
    public function set_seriousgame_mode()
5651
    {
5652
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5653
        $sql = "SELECT * FROM $lp_table
5654
                WHERE iid = ".$this->get_id();
5655
        $res = Database::query($sql);
5656
        if (Database::num_rows($res) > 0) {
5657
            $row = Database::fetch_array($res);
5658
            $force = $row['seriousgame_mode'];
5659
            if ($force == 1) {
5660
                $force = 0;
5661
            } elseif ($force == 0) {
5662
                $force = 1;
5663
            }
5664
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5665
			        WHERE iid = ".$this->get_id();
5666
            Database::query($sql);
5667
            $this->seriousgame_mode = $force;
5668
5669
            return $force;
5670
        }
5671
5672
        return -1;
5673
    }
5674
5675
    /**
5676
     * Updates the "scorm_debug" value that shows or hide the debug window.
5677
     *
5678
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5679
     */
5680
    public function update_scorm_debug()
5681
    {
5682
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5683
        $sql = "SELECT * FROM $lp_table
5684
                WHERE iid = ".$this->get_id();
5685
        $res = Database::query($sql);
5686
        if (Database::num_rows($res) > 0) {
5687
            $row = Database::fetch_array($res);
5688
            $force = $row['debug'];
5689
            if ($force == 1) {
5690
                $force = 0;
5691
            } elseif ($force == 0) {
5692
                $force = 1;
5693
            }
5694
            $sql = "UPDATE $lp_table SET debug = $force
5695
                    WHERE iid = ".$this->get_id();
5696
            Database::query($sql);
5697
            $this->scorm_debug = $force;
5698
5699
            return $force;
5700
        }
5701
5702
        return -1;
5703
    }
5704
5705
    /**
5706
     * Function that makes a call to the function sort_tree_array and create_tree_array.
5707
     *
5708
     * @author Kevin Van Den Haute
5709
     *
5710
     * @param  array
5711
     */
5712
    public function tree_array($array)
5713
    {
5714
        $array = $this->sort_tree_array($array);
5715
        $this->create_tree_array($array);
5716
    }
5717
5718
    /**
5719
     * Creates an array with the elements of the learning path tree in it.
5720
     *
5721
     * @author Kevin Van Den Haute
5722
     *
5723
     * @param array $array
5724
     * @param int   $parent
5725
     * @param int   $depth
5726
     * @param array $tmp
5727
     */
5728
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
5729
    {
5730
        if (is_array($array)) {
5731
            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...
5732
                if ($array[$i]['parent_item_id'] == $parent) {
5733
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
5734
                        $tmp[] = $array[$i]['parent_item_id'];
5735
                        $depth++;
5736
                    }
5737
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
5738
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
5739
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
5740
5741
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
5742
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
5743
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
5744
                    $this->arrMenu[] = [
5745
                        'id' => $array[$i]['id'],
5746
                        'ref' => $ref,
5747
                        'item_type' => $array[$i]['item_type'],
5748
                        'title' => $array[$i]['title'],
5749
                        'title_raw' => $array[$i]['title_raw'],
5750
                        'path' => $path,
5751
                        'description' => $array[$i]['description'],
5752
                        'parent_item_id' => $array[$i]['parent_item_id'],
5753
                        'previous_item_id' => $array[$i]['previous_item_id'],
5754
                        'next_item_id' => $array[$i]['next_item_id'],
5755
                        'min_score' => $array[$i]['min_score'],
5756
                        'max_score' => $array[$i]['max_score'],
5757
                        'mastery_score' => $array[$i]['mastery_score'],
5758
                        'display_order' => $array[$i]['display_order'],
5759
                        'prerequisite' => $preq,
5760
                        'depth' => $depth,
5761
                        'audio' => $audio,
5762
                        'prerequisite_min_score' => $prerequisiteMinScore,
5763
                        'prerequisite_max_score' => $prerequisiteMaxScore,
5764
                    ];
5765
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
5766
                }
5767
            }
5768
        }
5769
    }
5770
5771
    /**
5772
     * Sorts a multi dimensional array by parent id and display order.
5773
     *
5774
     * @author Kevin Van Den Haute
5775
     *
5776
     * @param array $array (array with al the learning path items in it)
5777
     *
5778
     * @return array
5779
     */
5780
    public function sort_tree_array($array)
5781
    {
5782
        foreach ($array as $key => $row) {
5783
            $parent[$key] = $row['parent_item_id'];
5784
            $position[$key] = $row['display_order'];
5785
        }
5786
5787
        if (count($array) > 0) {
5788
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
5789
        }
5790
5791
        return $array;
5792
    }
5793
5794
    /**
5795
     * Function that creates a html list of learning path items so that we can add audio files to them.
5796
     *
5797
     * @author Kevin Van Den Haute
5798
     *
5799
     * @return string
5800
     */
5801
    public function overview()
5802
    {
5803
        $return = '';
5804
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
5805
5806
        // we need to start a form when we want to update all the mp3 files
5807
        if ($update_audio == 'true') {
5808
            $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">';
5809
        }
5810
        $return .= '<div id="message"></div>';
5811
        if (count($this->items) == 0) {
5812
            $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');
5813
        } else {
5814
            $return_audio = '<table class="data_table">';
5815
            $return_audio .= '<tr>';
5816
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
5817
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
5818
            $return_audio .= '</tr>';
5819
5820
            if ($update_audio != 'true') {
5821
                $return .= '<div class="col-md-12">';
5822
                $return .= self::return_new_tree($update_audio);
5823
                $return .= '</div>';
5824
                $return .= Display::div(
5825
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
5826
                    ['style' => 'float:left; margin-top:15px;width:100%']
5827
                );
5828
            } else {
5829
                $return_audio .= self::return_new_tree($update_audio);
5830
                $return .= $return_audio.'</table>';
5831
            }
5832
5833
            // We need to close the form when we are updating the mp3 files.
5834
            if ($update_audio == 'true') {
5835
                $return .= '<div class="footer-audio">';
5836
                $return .= Display::button(
5837
                    'save_audio',
5838
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('Save audio and organization'),
5839
                    ['class' => 'btn btn-primary', 'type' => 'submit']
5840
                );
5841
                $return .= '</div>';
5842
            }
5843
        }
5844
5845
        // We need to close the form when we are updating the mp3 files.
5846
        if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
5847
            $return .= '</form>';
5848
        }
5849
5850
        return $return;
5851
    }
5852
5853
    /**
5854
     * @param string $update_audio
5855
     *
5856
     * @return array
5857
     */
5858
    public function processBuildMenuElements($update_audio = 'false')
5859
    {
5860
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
5861
        $arrLP = $this->getItemsForForm();
5862
5863
        $this->tree_array($arrLP);
5864
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
5865
        unset($this->arrMenu);
5866
        $default_data = null;
5867
        $default_content = null;
5868
        $elements = [];
5869
        $return_audio = null;
5870
        $iconPath = api_get_path(SYS_CODE_PATH).'img/';
5871
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
5872
        $countItems = count($arrLP);
5873
5874
        $upIcon = Display::return_icon(
5875
            'up.png',
5876
            get_lang('Up'),
5877
            [],
5878
            ICON_SIZE_TINY
5879
        );
5880
5881
        $disableUpIcon = Display::return_icon(
5882
            'up_na.png',
5883
            get_lang('Up'),
5884
            [],
5885
            ICON_SIZE_TINY
5886
        );
5887
5888
        $downIcon = Display::return_icon(
5889
            'down.png',
5890
            get_lang('Down'),
5891
            [],
5892
            ICON_SIZE_TINY
5893
        );
5894
5895
        $disableDownIcon = Display::return_icon(
5896
            'down_na.png',
5897
            get_lang('Down'),
5898
            [],
5899
            ICON_SIZE_TINY
5900
        );
5901
5902
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
5903
5904
        $pluginCalendar = api_get_plugin_setting('learning_calendar', 'enabled') === 'true';
5905
        $plugin = null;
5906
        if ($pluginCalendar) {
5907
            $plugin = LearningCalendarPlugin::create();
5908
        }
5909
5910
        for ($i = 0; $i < $countItems; $i++) {
5911
            $parent_id = $arrLP[$i]['parent_item_id'];
5912
            $title = $arrLP[$i]['title'];
5913
            $title_cut = $arrLP[$i]['title_raw'];
5914
            if ($show === false) {
5915
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
5916
            }
5917
            // Link for the documents
5918
            if ($arrLP[$i]['item_type'] === 'document' || $arrLP[$i]['item_type'] == TOOL_READOUT_TEXT) {
5919
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5920
                $title_cut = Display::url(
5921
                    $title_cut,
5922
                    $url,
5923
                    [
5924
                        'class' => 'ajax moved',
5925
                        'data-title' => $title,
5926
                        'title' => $title,
5927
                    ]
5928
                );
5929
            }
5930
5931
            // Detect if type is FINAL_ITEM to set path_id to SESSION
5932
            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
5933
                Session::write('pathItem', $arrLP[$i]['path']);
5934
            }
5935
5936
            $oddClass = 'row_even';
5937
            if (($i % 2) == 0) {
5938
                $oddClass = 'row_odd';
5939
            }
5940
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
5941
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
5942
5943
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
5944
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
5945
            } else {
5946
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
5947
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
5948
                } else {
5949
                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
5950
                        $icon = Display::return_icon('certificate.png');
5951
                    } else {
5952
                        $icon = Display::return_icon('folder_document.png');
5953
                    }
5954
                }
5955
            }
5956
5957
            // The audio column.
5958
            $return_audio .= '<td align="left" style="padding-left:10px;">';
5959
            $audio = '';
5960
            if (!$update_audio || $update_audio != 'true') {
5961
                if (empty($arrLP[$i]['audio'])) {
5962
                    $audio .= '';
5963
                }
5964
            } else {
5965
                $types = self::getChapterTypes();
5966
                if (!in_array($arrLP[$i]['item_type'], $types)) {
5967
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
5968
                    if (!empty($arrLP[$i]['audio'])) {
5969
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
5970
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('Remove audio');
5971
                    }
5972
                }
5973
            }
5974
5975
            $return_audio .= Display::span($icon.' '.$title).
5976
                Display::tag(
5977
                    'td',
5978
                    $audio,
5979
                    ['style' => '']
5980
                );
5981
            $return_audio .= '</td>';
5982
            $move_icon = '';
5983
            $move_item_icon = '';
5984
            $edit_icon = '';
5985
            $delete_icon = '';
5986
            $audio_icon = '';
5987
            $prerequisities_icon = '';
5988
            $forumIcon = '';
5989
            $previewIcon = '';
5990
            $pluginCalendarIcon = '';
5991
            $orderIcons = '';
5992
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
5993
5994
            if ($is_allowed_to_edit) {
5995
                if (!$update_audio || $update_audio != 'true') {
5996
                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
5997
                        $move_icon .= '<a class="moved" href="#">';
5998
                        $move_icon .= Display::return_icon(
5999
                            'move_everywhere.png',
6000
                            get_lang('Move'),
6001
                            [],
6002
                            ICON_SIZE_TINY
6003
                        );
6004
                        $move_icon .= '</a>';
6005
                    }
6006
                }
6007
6008
                // No edit for this item types
6009
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6010
                    if ($arrLP[$i]['item_type'] != 'dir') {
6011
                        $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">';
6012
                        $edit_icon .= Display::return_icon(
6013
                            'edit.png',
6014
                            get_lang('Edit section description/name'),
6015
                            [],
6016
                            ICON_SIZE_TINY
6017
                        );
6018
                        $edit_icon .= '</a>';
6019
6020
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6021
                            $forumThread = null;
6022
                            if (isset($this->items[$arrLP[$i]['id']])) {
6023
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6024
                                    $this->course_int_id,
6025
                                    $this->lp_session_id
6026
                                );
6027
                            }
6028
                            if ($forumThread) {
6029
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6030
                                        'action' => 'dissociate_forum',
6031
                                        'id' => $arrLP[$i]['id'],
6032
                                        'lp_id' => $this->lp_id,
6033
                                    ]);
6034
                                $forumIcon = Display::url(
6035
                                    Display::return_icon(
6036
                                        'forum.png',
6037
                                        get_lang('Dissociate the forum of this learning path item'),
6038
                                        [],
6039
                                        ICON_SIZE_TINY
6040
                                    ),
6041
                                    $forumIconUrl,
6042
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6043
                                );
6044
                            } else {
6045
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6046
                                        'action' => 'create_forum',
6047
                                        'id' => $arrLP[$i]['id'],
6048
                                        'lp_id' => $this->lp_id,
6049
                                    ]);
6050
                                $forumIcon = Display::url(
6051
                                    Display::return_icon(
6052
                                        'forum.png',
6053
                                        get_lang('Associate a forum to this learning path item'),
6054
                                        [],
6055
                                        ICON_SIZE_TINY
6056
                                    ),
6057
                                    $forumIconUrl,
6058
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6059
                                );
6060
                            }
6061
                        }
6062
                    } else {
6063
                        $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">';
6064
                        $edit_icon .= Display::return_icon(
6065
                            'edit.png',
6066
                            get_lang('Edit section description/name'),
6067
                            [],
6068
                            ICON_SIZE_TINY
6069
                        );
6070
                        $edit_icon .= '</a>';
6071
                    }
6072
                } else {
6073
                    if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6074
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6075
                        $edit_icon .= Display::return_icon(
6076
                            'edit.png',
6077
                            get_lang('Edit'),
6078
                            [],
6079
                            ICON_SIZE_TINY
6080
                        );
6081
                        $edit_icon .= '</a>';
6082
                    }
6083
                }
6084
6085
                if ($pluginCalendar) {
6086
                    $pluginLink = $pluginUrl.
6087
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6088
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('1 day'), [], ICON_SIZE_TINY);
6089
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6090
                    if ($itemInfo && $itemInfo['value'] == 1) {
6091
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('1 day'), [], ICON_SIZE_TINY);
6092
                    }
6093
                    $pluginCalendarIcon = Display::url(
6094
                        $iconCalendar,
6095
                        $pluginLink,
6096
                        ['class' => 'btn btn-default']
6097
                    );
6098
                }
6099
6100
                if ($arrLP[$i]['item_type'] != 'final_item') {
6101
                    $orderIcons = Display::url(
6102
                        $upIcon,
6103
                        'javascript:void(0)',
6104
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
6105
                    );
6106
                    $orderIcons .= Display::url(
6107
                        $downIcon,
6108
                        'javascript:void(0)',
6109
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
6110
                    );
6111
                }
6112
6113
                $delete_icon .= ' <a
6114
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
6115
                    onclick="return confirmation(\''.addslashes($title).'\');"
6116
                    class="btn btn-default">';
6117
                $delete_icon .= Display::return_icon(
6118
                    'delete.png',
6119
                    get_lang('Delete section'),
6120
                    [],
6121
                    ICON_SIZE_TINY
6122
                );
6123
                $delete_icon .= '</a>';
6124
6125
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6126
                $previewImage = Display::return_icon(
6127
                    'preview_view.png',
6128
                    get_lang('Preview'),
6129
                    [],
6130
                    ICON_SIZE_TINY
6131
                );
6132
6133
                switch ($arrLP[$i]['item_type']) {
6134
                    case TOOL_DOCUMENT:
6135
                    case TOOL_LP_FINAL_ITEM:
6136
                    case TOOL_READOUT_TEXT:
6137
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6138
                        $previewIcon = Display::url(
6139
                            $previewImage,
6140
                            $urlPreviewLink,
6141
                            [
6142
                                'target' => '_blank',
6143
                                'class' => 'btn btn-default',
6144
                                'data-title' => $arrLP[$i]['title'],
6145
                                'title' => $arrLP[$i]['title'],
6146
                            ]
6147
                        );
6148
                        break;
6149
                    case TOOL_THREAD:
6150
                    case TOOL_FORUM:
6151
                    case TOOL_QUIZ:
6152
                    case TOOL_STUDENTPUBLICATION:
6153
                    case TOOL_LP_FINAL_ITEM:
6154
                    case TOOL_LINK:
6155
                        $class = 'btn btn-default';
6156
                        $target = '_blank';
6157
                        $link = self::rl_get_resource_link_for_learnpath(
6158
                            $this->course_int_id,
6159
                            $this->lp_id,
6160
                            $arrLP[$i]['id'],
6161
                            0
6162
                        );
6163
                        $previewIcon = Display::url(
6164
                            $previewImage,
6165
                            $link,
6166
                            [
6167
                                'class' => $class,
6168
                                'data-title' => $arrLP[$i]['title'],
6169
                                'title' => $arrLP[$i]['title'],
6170
                                'target' => $target,
6171
                            ]
6172
                        );
6173
                        break;
6174
                    default:
6175
                        $previewIcon = Display::url(
6176
                            $previewImage,
6177
                            $url.'&action=view_item',
6178
                            ['class' => 'btn btn-default', 'target' => '_blank']
6179
                        );
6180
                        break;
6181
                }
6182
6183
                if ($arrLP[$i]['item_type'] != 'dir') {
6184
                    $prerequisities_icon = Display::url(
6185
                        Display::return_icon(
6186
                            'accept.png',
6187
                            get_lang('Prerequisites'),
6188
                            [],
6189
                            ICON_SIZE_TINY
6190
                        ),
6191
                        $url.'&action=edit_item_prereq',
6192
                        ['class' => 'btn btn-default']
6193
                    );
6194
                    if ($arrLP[$i]['item_type'] != 'final_item') {
6195
                        $move_item_icon = Display::url(
6196
                            Display::return_icon(
6197
                                'move.png',
6198
                                get_lang('Move'),
6199
                                [],
6200
                                ICON_SIZE_TINY
6201
                            ),
6202
                            $url.'&action=move_item',
6203
                            ['class' => 'btn btn-default']
6204
                        );
6205
                    }
6206
                    $audio_icon = Display::url(
6207
                        Display::return_icon(
6208
                            'audio.png',
6209
                            get_lang('Upload'),
6210
                            [],
6211
                            ICON_SIZE_TINY
6212
                        ),
6213
                        $url.'&action=add_audio',
6214
                        ['class' => 'btn btn-default']
6215
                    );
6216
                }
6217
            }
6218
            if ($update_audio != 'true') {
6219
                $row = $move_icon.' '.$icon.
6220
                    Display::span($title_cut).
6221
                    Display::tag(
6222
                        'div',
6223
                        "<div class=\"btn-group btn-group-xs\">
6224
                                    $previewIcon
6225
                                    $audio
6226
                                    $edit_icon
6227
                                    $pluginCalendarIcon
6228
                                    $forumIcon
6229
                                    $prerequisities_icon
6230
                                    $move_item_icon
6231
                                    $audio_icon
6232
                                    $orderIcons
6233
                                    $delete_icon
6234
                                </div>",
6235
                        ['class' => 'btn-toolbar button_actions']
6236
                    );
6237
            } else {
6238
                $row =
6239
                    Display::span($title.$icon).
6240
                    Display::span($audio, ['class' => 'button_actions']);
6241
            }
6242
6243
            $default_data[$arrLP[$i]['id']] = $row;
6244
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6245
6246
            if (empty($parent_id)) {
6247
                $elements[$arrLP[$i]['id']]['data'] = $row;
6248
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6249
            } else {
6250
                $parent_arrays = [];
6251
                if ($arrLP[$i]['depth'] > 1) {
6252
                    // Getting list of parents
6253
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6254
                        foreach ($arrLP as $item) {
6255
                            if ($item['id'] == $parent_id) {
6256
                                if ($item['parent_item_id'] == 0) {
6257
                                    $parent_id = $item['id'];
6258
                                    break;
6259
                                } else {
6260
                                    $parent_id = $item['parent_item_id'];
6261
                                    if (empty($parent_arrays)) {
6262
                                        $parent_arrays[] = intval($item['id']);
6263
                                    }
6264
                                    $parent_arrays[] = $parent_id;
6265
                                    break;
6266
                                }
6267
                            }
6268
                        }
6269
                    }
6270
                }
6271
6272
                if (!empty($parent_arrays)) {
6273
                    $parent_arrays = array_reverse($parent_arrays);
6274
                    $val = '$elements';
6275
                    $x = 0;
6276
                    foreach ($parent_arrays as $item) {
6277
                        if ($x != count($parent_arrays) - 1) {
6278
                            $val .= '["'.$item.'"]["children"]';
6279
                        } else {
6280
                            $val .= '["'.$item.'"]["children"]';
6281
                        }
6282
                        $x++;
6283
                    }
6284
                    $val .= "";
6285
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6286
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6287
                } else {
6288
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6289
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6290
                }
6291
            }
6292
        }
6293
6294
        return [
6295
            'elements' => $elements,
6296
            'default_data' => $default_data,
6297
            'default_content' => $default_content,
6298
            'return_audio' => $return_audio,
6299
        ];
6300
    }
6301
6302
    /**
6303
     * @param string $updateAudio true/false strings
6304
     *
6305
     * @return string
6306
     */
6307
    public function returnLpItemList($updateAudio)
6308
    {
6309
        $result = $this->processBuildMenuElements($updateAudio);
6310
6311
        $html = self::print_recursive(
6312
            $result['elements'],
6313
            $result['default_data'],
6314
            $result['default_content']
6315
        );
6316
6317
        if (!empty($html)) {
6318
            $html .= Display::return_message(get_lang('Drag and drop an element here'));
6319
        }
6320
6321
        return $html;
6322
    }
6323
6324
    /**
6325
     * @param string $update_audio
6326
     * @param bool   $drop_element_here
6327
     *
6328
     * @return string
6329
     */
6330
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6331
    {
6332
        $result = $this->processBuildMenuElements($update_audio);
6333
6334
        $list = '<ul id="lp_item_list">';
6335
        $tree = $this->print_recursive(
6336
            $result['elements'],
6337
            $result['default_data'],
6338
            $result['default_content']
6339
        );
6340
6341
        if (!empty($tree)) {
6342
            $list .= $tree;
6343
        } else {
6344
            if ($drop_element_here) {
6345
                $list .= Display::return_message(get_lang('Drag and drop an element here'));
6346
            }
6347
        }
6348
        $list .= '</ul>';
6349
6350
        $return = Display::panelCollapse(
6351
            $this->name,
6352
            $list,
6353
            'scorm-list',
6354
            null,
6355
            'scorm-list-accordion',
6356
            'scorm-list-collapse'
6357
        );
6358
6359
        if ($update_audio === 'true') {
6360
            $return = $result['return_audio'];
6361
        }
6362
6363
        return $return;
6364
    }
6365
6366
    /**
6367
     * @param array $elements
6368
     * @param array $default_data
6369
     * @param array $default_content
6370
     *
6371
     * @return string
6372
     */
6373
    public function print_recursive($elements, $default_data, $default_content)
6374
    {
6375
        $return = '';
6376
        foreach ($elements as $key => $item) {
6377
            if (isset($item['load_data']) || empty($item['data'])) {
6378
                $item['data'] = $default_data[$item['load_data']];
6379
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6380
            }
6381
            $sub_list = '';
6382
            if (isset($item['type']) && $item['type'] === 'dir') {
6383
                // empty value
6384
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6385
            }
6386
            if (empty($item['children'])) {
6387
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6388
                $active = null;
6389
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6390
                    $active = 'active';
6391
                }
6392
                $return .= Display::tag(
6393
                    'li',
6394
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6395
                    ['id' => $key, 'class' => 'record li_container']
6396
                );
6397
            } else {
6398
                // Sections
6399
                $data = '';
6400
                if (isset($item['children'])) {
6401
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6402
                }
6403
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6404
                $return .= Display::tag(
6405
                    'li',
6406
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6407
                    ['id' => $key, 'class' => 'record li_container']
6408
                );
6409
            }
6410
        }
6411
6412
        return $return;
6413
    }
6414
6415
    /**
6416
     * This function builds the action menu.
6417
     *
6418
     * @param bool $returnContent          Optional
6419
     * @param bool $showRequirementButtons Optional. Allow show the requirements button
6420
     * @param bool $isConfigPage           Optional. If is the config page, show the edit button
6421
     * @param bool $allowExpand            Optional. Allow show the expand/contract button
6422
     *
6423
     * @return string
6424
     */
6425
    public function build_action_menu(
6426
        $returnContent = false,
6427
        $showRequirementButtons = true,
6428
        $isConfigPage = false,
6429
        $allowExpand = true
6430
    ) {
6431
        $actionsRight = '';
6432
        $actionsLeft = Display::url(
6433
            Display::return_icon(
6434
                'back.png',
6435
                get_lang('Back to learning paths'),
6436
                '',
6437
                ICON_SIZE_MEDIUM
6438
            ),
6439
            'lp_controller.php?'.api_get_cidreq()
6440
        );
6441
        $actionsLeft .= Display::url(
6442
            Display::return_icon(
6443
                'preview_view.png',
6444
                get_lang('Preview'),
6445
                '',
6446
                ICON_SIZE_MEDIUM
6447
            ),
6448
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6449
                'action' => 'view',
6450
                'lp_id' => $this->lp_id,
6451
                'isStudentView' => 'true',
6452
            ])
6453
        );
6454
6455
        $actionsLeft .= Display::url(
6456
            Display::return_icon(
6457
                'upload_audio.png',
6458
                get_lang('Add audio'),
6459
                '',
6460
                ICON_SIZE_MEDIUM
6461
            ),
6462
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6463
                'action' => 'admin_view',
6464
                'lp_id' => $this->lp_id,
6465
                'updateaudio' => 'true',
6466
            ])
6467
        );
6468
6469
        $subscriptionSettings = self::getSubscriptionSettings();
6470
6471
        $request = api_request_uri();
6472
        if (strpos($request, 'edit') === false) {
6473
            $actionsLeft .= Display::url(
6474
                Display::return_icon(
6475
                    'settings.png',
6476
                    get_lang('Course settings'),
6477
                    '',
6478
                    ICON_SIZE_MEDIUM
6479
                ),
6480
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6481
                    'action' => 'edit',
6482
                    'lp_id' => $this->lp_id,
6483
                ])
6484
            );
6485
        }
6486
6487
        if (strpos($request, 'build') === false && strpos($request, 'add_item') === false) {
6488
            $actionsLeft .= Display::url(
6489
                Display::return_icon(
6490
                    'edit.png',
6491
                    get_lang('Edit'),
6492
                    '',
6493
                    ICON_SIZE_MEDIUM
6494
                ),
6495
                'lp_controller.php?'.http_build_query([
6496
                    'action' => 'build',
6497
                    'lp_id' => $this->lp_id,
6498
                ]).'&'.api_get_cidreq()
6499
            );
6500
        }
6501
6502
        if (strpos(api_get_self(), 'lp_subscribe_users.php') === false) {
6503
            if ($this->subscribeUsers == 1 &&
6504
                $subscriptionSettings['allow_add_users_to_lp']) {
6505
                $actionsLeft .= Display::url(
6506
                    Display::return_icon(
6507
                        'user.png',
6508
                        get_lang('Subscribe users to learning path'),
6509
                        '',
6510
                        ICON_SIZE_MEDIUM
6511
                    ),
6512
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=".$this->lp_id."&".api_get_cidreq()
6513
                );
6514
            }
6515
        }
6516
6517
        if ($allowExpand) {
6518
            $actionsLeft .= Display::url(
6519
                Display::return_icon(
6520
                    'expand.png',
6521
                    get_lang('Expand'),
6522
                    ['id' => 'expand'],
6523
                    ICON_SIZE_MEDIUM
6524
                ).
6525
                Display::return_icon(
6526
                    'contract.png',
6527
                    get_lang('Collapse'),
6528
                    ['id' => 'contract', 'class' => 'hide'],
6529
                    ICON_SIZE_MEDIUM
6530
                ),
6531
                '#',
6532
                ['role' => 'button', 'id' => 'hide_bar_template']
6533
            );
6534
        }
6535
6536
        if ($showRequirementButtons) {
6537
            $buttons = [
6538
                [
6539
                    'title' => get_lang('Set previous step as prerequisite for each step'),
6540
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6541
                        'action' => 'set_previous_step_as_prerequisite',
6542
                        'lp_id' => $this->lp_id,
6543
                    ]),
6544
                ],
6545
                [
6546
                    'title' => get_lang('Clear all prerequisites'),
6547
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6548
                        'action' => 'clear_prerequisites',
6549
                        'lp_id' => $this->lp_id,
6550
                    ]),
6551
                ],
6552
            ];
6553
            $actionsRight = Display::groupButtonWithDropDown(
6554
                get_lang('Prerequisites options'),
6555
                $buttons,
6556
                true
6557
            );
6558
        }
6559
6560
        $toolbar = Display::toolbarAction(
6561
            'actions-lp-controller',
6562
            [$actionsLeft, $actionsRight]
6563
        );
6564
6565
        if ($returnContent) {
6566
            return $toolbar;
6567
        }
6568
6569
        echo $toolbar;
6570
    }
6571
6572
    /**
6573
     * Creates the default learning path folder.
6574
     *
6575
     * @param array $course
6576
     * @param int   $creatorId
6577
     *
6578
     * @return bool
6579
     */
6580
    public static function generate_learning_path_folder($course, $creatorId = 0)
6581
    {
6582
        // Creating learning_path folder
6583
        $dir = 'learning_path';
6584
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6585
        $folder = false;
6586
        $folderData = create_unexisting_directory(
6587
            $course,
6588
            $creatorId,
6589
            0,
6590
            null,
6591
            0,
6592
            '',
6593
            $dir,
6594
            get_lang('Learning paths'),
6595
            0
6596
        );
6597
6598
        if (!empty($folderData)) {
6599
            $folder = true;
6600
        }
6601
6602
        return $folder;
6603
    }
6604
6605
    /**
6606
     * @param array  $course
6607
     * @param string $lp_name
6608
     * @param int    $creatorId
6609
     *
6610
     * @return array
6611
     */
6612
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6613
    {
6614
        $filepath = '';
6615
        $dir = '/learning_path/';
6616
6617
        if (empty($lp_name)) {
6618
            $lp_name = $this->name;
6619
        }
6620
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6621
        $folder = self::generate_learning_path_folder($course, $creatorId);
6622
6623
        // Limits title size
6624
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6625
        $dir = $dir.$title;
6626
6627
        // Creating LP folder
6628
        $documentId = null;
6629
        if ($folder) {
6630
            $folderData = create_unexisting_directory(
6631
                $course,
6632
                $creatorId,
6633
                0,
6634
                0,
6635
                0,
6636
                $filepath,
6637
                $dir,
6638
                $lp_name
6639
            );
6640
            if (!empty($folderData)) {
6641
                $folder = true;
6642
            }
6643
6644
            $documentId = $folderData->getIid();
6645
            $dir = $dir.'/';
6646
            if ($folder) {
6647
                // $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6648
            }
6649
        }
6650
6651
        if (empty($documentId)) {
6652
            $dir = api_remove_trailing_slash($dir);
6653
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6654
        }
6655
6656
        $array = [
6657
            'dir' => $dir,
6658
            'filepath' => $filepath,
6659
            'folder' => $folder,
6660
            'id' => $documentId,
6661
        ];
6662
6663
        return $array;
6664
    }
6665
6666
    /**
6667
     * Create a new document //still needs some finetuning.
6668
     *
6669
     * @param array  $courseInfo
6670
     * @param string $content
6671
     * @param string $title
6672
     * @param string $extension
6673
     * @param int    $parentId
6674
     * @param int    $creatorId  creator id
6675
     *
6676
     * @return int
6677
     */
6678
    public function create_document(
6679
        $courseInfo,
6680
        $content = '',
6681
        $title = '',
6682
        $extension = 'html',
6683
        $parentId = 0,
6684
        $creatorId = 0
6685
    ) {
6686
        if (!empty($courseInfo)) {
6687
            $course_id = $courseInfo['real_id'];
6688
        } else {
6689
            $course_id = api_get_course_int_id();
6690
        }
6691
6692
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6693
        $sessionId = api_get_session_id();
6694
6695
        // Generates folder
6696
        $result = $this->generate_lp_folder($courseInfo);
6697
        $dir = $result['dir'];
6698
6699
        if (empty($parentId) || $parentId == '/') {
6700
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6701
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6702
6703
            if ($parentId === '/') {
6704
                $dir = '/';
6705
            }
6706
6707
            // Please, do not modify this dirname formatting.
6708
            if (strstr($dir, '..')) {
6709
                $dir = '/';
6710
            }
6711
6712
            if (!empty($dir[0]) && $dir[0] == '.') {
6713
                $dir = substr($dir, 1);
6714
            }
6715
            if (!empty($dir[0]) && $dir[0] != '/') {
6716
                $dir = '/'.$dir;
6717
            }
6718
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
6719
                $dir .= '/';
6720
            }
6721
        } else {
6722
            $parentInfo = DocumentManager::get_document_data_by_id(
6723
                $parentId,
6724
                $courseInfo['code']
6725
            );
6726
            if (!empty($parentInfo)) {
6727
                $dir = $parentInfo['path'].'/';
6728
            }
6729
        }
6730
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6731
        // is already escaped twice when it gets here.
6732
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6733
        if (!empty($title)) {
6734
            $title = api_replace_dangerous_char(stripslashes($title));
6735
        } else {
6736
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6737
        }
6738
6739
        $title = disable_dangerous_file($title);
6740
        $filename = $title;
6741
        $content = !empty($content) ? $content : $_POST['content_lp'];
6742
        $tmp_filename = $filename;
6743
        $filename = $tmp_filename.'.'.$extension;
6744
6745
        if ($extension === 'html') {
6746
            $content = stripslashes($content);
6747
            $content = str_replace(
6748
                api_get_path(WEB_COURSE_PATH),
6749
                api_get_path(REL_PATH).'courses/',
6750
                $content
6751
            );
6752
6753
            // Change the path of mp3 to absolute.
6754
            // The first regexp deals with :// urls.
6755
            $content = preg_replace(
6756
                "|(flashvars=\"file=)([^:/]+)/|",
6757
                "$1".api_get_path(
6758
                    REL_COURSE_PATH
6759
                ).$courseInfo['path'].'/document/',
6760
                $content
6761
            );
6762
            // The second regexp deals with audio/ urls.
6763
            $content = preg_replace(
6764
                "|(flashvars=\"file=)([^/]+)/|",
6765
                "$1".api_get_path(
6766
                    REL_COURSE_PATH
6767
                ).$courseInfo['path'].'/document/$2/',
6768
                $content
6769
            );
6770
            // For flv player: To prevent edition problem with firefox,
6771
            // we have to use a strange tip (don't blame me please).
6772
            $content = str_replace(
6773
                '</body>',
6774
                '<style type="text/css">body{}</style></body>',
6775
                $content
6776
            );
6777
        }
6778
6779
        $save_file_path = $dir.$filename;
6780
6781
        $document = DocumentManager::addDocument(
6782
            $courseInfo,
6783
            $save_file_path,
6784
            'file',
6785
            '',
6786
            $tmp_filename,
6787
            '',
6788
            0, //readonly
6789
            true,
6790
            null,
6791
            $sessionId,
6792
            $creatorId,
6793
            false,
6794
            $content,
6795
            $parentId
6796
        );
6797
6798
        $document_id = $document->getIid();
6799
        if ($document_id) {
6800
            $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
6801
            $new_title = $originalTitle;
6802
6803
            if ($new_comment || $new_title) {
6804
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
6805
                $ct = '';
6806
                if ($new_comment) {
6807
                    $ct .= ", comment='".Database::escape_string($new_comment)."'";
6808
                }
6809
                if ($new_title) {
6810
                    $ct .= ", title='".Database::escape_string($new_title)."' ";
6811
                }
6812
6813
                $sql = "UPDATE $tbl_doc SET ".substr($ct, 1)."
6814
                        WHERE c_id = $course_id AND id = $document_id ";
6815
                Database::query($sql);
6816
            }
6817
        }
6818
6819
        return $document_id;
6820
    }
6821
6822
    /**
6823
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
6824
     *
6825
     * @param array $_course array
6826
     */
6827
    public function edit_document($_course)
6828
    {
6829
        $course_id = api_get_course_int_id();
6830
        $urlAppend = '';
6831
        // Please, do not modify this dirname formatting.
6832
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
6833
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
6834
6835
        if (strstr($dir, '..')) {
6836
            $dir = '/';
6837
        }
6838
6839
        if (isset($dir[0]) && $dir[0] == '.') {
6840
            $dir = substr($dir, 1);
6841
        }
6842
6843
        if (isset($dir[0]) && $dir[0] != '/') {
6844
            $dir = '/'.$dir;
6845
        }
6846
6847
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
6848
            $dir .= '/';
6849
        }
6850
6851
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
6852
        if (!is_dir($filepath)) {
6853
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
6854
        }
6855
6856
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
6857
6858
        if (isset($_POST['path']) && !empty($_POST['path'])) {
6859
            $document_id = (int) $_POST['path'];
6860
            $documentInfo = DocumentManager::get_document_data_by_id($document_id, api_get_course_id(), false, null, true);
6861
            if (empty($documentInfo)) {
6862
                // Try with iid
6863
                $table = Database::get_course_table(TABLE_DOCUMENT);
6864
                $sql = "SELECT id, path FROM $table
6865
                        WHERE c_id = $course_id AND iid = $document_id AND path NOT LIKE '%_DELETED_%' ";
6866
                $res_doc = Database::query($sql);
6867
                $row = Database::fetch_array($res_doc);
6868
                if ($row) {
6869
                    $document_id = $row['id'];
6870
                    $documentPath = $row['path'];
6871
                }
6872
            } else {
6873
                $documentPath = $documentInfo['path'];
6874
            }
6875
6876
            $content = stripslashes($_POST['content_lp']);
6877
            $file = $filepath.$documentPath;
6878
6879
            if (!file_exists($file)) {
6880
                return false;
6881
            }
6882
6883
            if ($fp = @fopen($file, 'w')) {
6884
                $content = str_replace(
6885
                    api_get_path(WEB_COURSE_PATH),
6886
                    $urlAppend.api_get_path(REL_COURSE_PATH),
6887
                    $content
6888
                );
6889
                // Change the path of mp3 to absolute.
6890
                // The first regexp deals with :// urls.
6891
                $content = preg_replace(
6892
                    "|(flashvars=\"file=)([^:/]+)/|",
6893
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
6894
                    $content
6895
                );
6896
                // The second regexp deals with audio/ urls.
6897
                $content = preg_replace(
6898
                    "|(flashvars=\"file=)([^:/]+)/|",
6899
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
6900
                    $content
6901
                );
6902
                fputs($fp, $content);
6903
                fclose($fp);
6904
6905
                $sql = "UPDATE $table_doc SET
6906
                            title='".Database::escape_string($_POST['title'])."'
6907
                        WHERE c_id = $course_id AND id = ".$document_id;
6908
                Database::query($sql);
6909
            }
6910
        }
6911
    }
6912
6913
    /**
6914
     * Displays the selected item, with a panel for manipulating the item.
6915
     *
6916
     * @param int    $item_id
6917
     * @param string $msg
6918
     * @param bool   $show_actions
6919
     *
6920
     * @return string
6921
     */
6922
    public function display_item($item_id, $msg = null, $show_actions = true)
6923
    {
6924
        $course_id = api_get_course_int_id();
6925
        $return = '';
6926
        if (is_numeric($item_id)) {
6927
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
6928
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
6929
                    WHERE lp.iid = ".intval($item_id);
6930
            $result = Database::query($sql);
6931
            while ($row = Database::fetch_array($result, 'ASSOC')) {
6932
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
6933
6934
                // Prevents wrong parent selection for document, see Bug#1251.
6935
                if ($row['item_type'] != 'dir') {
6936
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
6937
                }
6938
6939
                if ($show_actions) {
6940
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
6941
                }
6942
                $return .= '<div style="padding:10px;">';
6943
6944
                if ($msg != '') {
6945
                    $return .= $msg;
6946
                }
6947
6948
                $return .= '<h3>'.$row['title'].'</h3>';
6949
6950
                switch ($row['item_type']) {
6951
                    case TOOL_THREAD:
6952
                        $link = $this->rl_get_resource_link_for_learnpath(
6953
                            $course_id,
6954
                            $row['lp_id'],
6955
                            $item_id,
6956
                            0
6957
                        );
6958
                        $return .= Display::url(
6959
                            get_lang('Go to thread'),
6960
                            $link,
6961
                            ['class' => 'btn btn-primary']
6962
                        );
6963
                        break;
6964
                    case TOOL_FORUM:
6965
                        $return .= Display::url(
6966
                            get_lang('Go to the forum'),
6967
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
6968
                            ['class' => 'btn btn-primary']
6969
                        );
6970
                        break;
6971
                    case TOOL_QUIZ:
6972
                        if (!empty($row['path'])) {
6973
                            $exercise = new Exercise();
6974
                            $exercise->read($row['path']);
6975
                            $return .= $exercise->description.'<br />';
6976
                            $return .= Display::url(
6977
                                get_lang('Go to exercise'),
6978
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
6979
                                ['class' => 'btn btn-primary']
6980
                            );
6981
                        }
6982
                        break;
6983
                    case TOOL_LP_FINAL_ITEM:
6984
                        $return .= $this->getSavedFinalItem();
6985
                        break;
6986
                    case TOOL_DOCUMENT:
6987
                    case TOOL_READOUT_TEXT:
6988
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
6989
                        $sql_doc = "SELECT path FROM $tbl_doc
6990
                                    WHERE c_id = $course_id AND iid = ".intval($row['path']);
6991
                        $result = Database::query($sql_doc);
6992
                        $path_file = Database::result($result, 0, 0);
6993
                        $path_parts = pathinfo($path_file);
6994
                        // TODO: Correct the following naive comparisons.
6995
                        if (in_array($path_parts['extension'], [
6996
                            'html',
6997
                            'txt',
6998
                            'png',
6999
                            'jpg',
7000
                            'JPG',
7001
                            'jpeg',
7002
                            'JPEG',
7003
                            'gif',
7004
                            'swf',
7005
                            'pdf',
7006
                            'htm',
7007
                        ])) {
7008
                            $return .= $this->display_document($row['path'], true, true);
7009
                        }
7010
                        break;
7011
                    case TOOL_HOTPOTATOES:
7012
                        $return .= $this->display_document($row['path'], false, true);
7013
                        break;
7014
                }
7015
                $return .= '</div>';
7016
            }
7017
        }
7018
7019
        return $return;
7020
    }
7021
7022
    /**
7023
     * Shows the needed forms for editing a specific item.
7024
     *
7025
     * @param int $item_id
7026
     *
7027
     * @throws Exception
7028
     * @throws HTML_QuickForm_Error
7029
     *
7030
     * @return string
7031
     */
7032
    public function display_edit_item($item_id)
7033
    {
7034
        $course_id = api_get_course_int_id();
7035
        $return = '';
7036
        $item_id = (int) $item_id;
7037
7038
        if (empty($item_id)) {
7039
            return '';
7040
        }
7041
7042
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7043
        $sql = "SELECT * FROM $tbl_lp_item
7044
                WHERE iid = ".$item_id;
7045
        $res = Database::query($sql);
7046
        $row = Database::fetch_array($res);
7047
        switch ($row['item_type']) {
7048
            case 'dir':
7049
            case 'asset':
7050
            case 'sco':
7051
                if (isset($_GET['view']) && $_GET['view'] == 'build') {
7052
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7053
                    $return .= $this->display_item_form(
7054
                        $row['item_type'],
7055
                        get_lang('Edit the current section').' :',
7056
                        'edit',
7057
                        $item_id,
7058
                        $row
7059
                    );
7060
                } else {
7061
                    $return .= $this->display_item_form(
7062
                        $row['item_type'],
7063
                        get_lang('Edit the current section').' :',
7064
                        'edit_item',
7065
                        $item_id,
7066
                        $row
7067
                    );
7068
                }
7069
                break;
7070
            case TOOL_DOCUMENT:
7071
            case TOOL_READOUT_TEXT:
7072
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7073
                $sql = "SELECT lp.*, doc.path as dir
7074
                        FROM $tbl_lp_item as lp
7075
                        LEFT JOIN $tbl_doc as doc
7076
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7077
                        WHERE
7078
                            doc.c_id = $course_id AND
7079
                            lp.iid = ".$item_id;
7080
                $res_step = Database::query($sql);
7081
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7082
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7083
7084
                if ($row['item_type'] === TOOL_DOCUMENT) {
7085
                    $return .= $this->display_document_form('edit', $item_id, $row_step);
7086
                }
7087
7088
                if ($row['item_type'] === TOOL_READOUT_TEXT) {
7089
                    $return .= $this->displayFrmReadOutText('edit', $item_id, $row_step);
7090
                }
7091
                break;
7092
            case TOOL_LINK:
7093
                $linkId = (int) $row['path'];
7094
                if (!empty($linkId)) {
7095
                    $table = Database::get_course_table(TABLE_LINK);
7096
                    $sql = 'SELECT url FROM '.$table.'
7097
                            WHERE c_id = '.$course_id.' AND iid = '.$linkId;
7098
                    $res_link = Database::query($sql);
7099
                    $row_link = Database::fetch_array($res_link);
7100
                    if (empty($row_link)) {
7101
                        // Try with id
7102
                        $sql = 'SELECT url FROM '.$table.'
7103
                                WHERE c_id = '.$course_id.' AND id = '.$linkId;
7104
                        $res_link = Database::query($sql);
7105
                        $row_link = Database::fetch_array($res_link);
7106
                    }
7107
7108
                    if (is_array($row_link)) {
7109
                        $row['url'] = $row_link['url'];
7110
                    }
7111
                }
7112
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7113
                $return .= $this->display_link_form('edit', $item_id, $row);
7114
                break;
7115
            case TOOL_LP_FINAL_ITEM:
7116
                Session::write('finalItem', true);
7117
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7118
                $sql = "SELECT lp.*, doc.path as dir
7119
                        FROM $tbl_lp_item as lp
7120
                        LEFT JOIN $tbl_doc as doc
7121
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7122
                        WHERE
7123
                            doc.c_id = $course_id AND
7124
                            lp.iid = ".$item_id;
7125
                $res_step = Database::query($sql);
7126
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7127
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7128
                $return .= $this->display_document_form('edit', $item_id, $row_step);
7129
                break;
7130
            case TOOL_QUIZ:
7131
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7132
                $return .= $this->display_quiz_form('edit', $item_id, $row);
7133
                break;
7134
            case TOOL_HOTPOTATOES:
7135
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7136
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7137
                break;
7138
            case TOOL_STUDENTPUBLICATION:
7139
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7140
                $return .= $this->display_student_publication_form('edit', $item_id, $row);
7141
                break;
7142
            case TOOL_FORUM:
7143
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7144
                $return .= $this->display_forum_form('edit', $item_id, $row);
7145
                break;
7146
            case TOOL_THREAD:
7147
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7148
                $return .= $this->display_thread_form('edit', $item_id, $row);
7149
                break;
7150
        }
7151
7152
        return $return;
7153
    }
7154
7155
    /**
7156
     * Function that displays a list with al the resources that
7157
     * could be added to the learning path.
7158
     *
7159
     * @throws Exception
7160
     * @throws HTML_QuickForm_Error
7161
     *
7162
     * @return bool
7163
     */
7164
    public function display_resources()
7165
    {
7166
        $course_code = api_get_course_id();
7167
7168
        // Get all the docs.
7169
        $documents = $this->get_documents(true);
7170
7171
        // Get all the exercises.
7172
        $exercises = $this->get_exercises();
7173
7174
        // Get all the links.
7175
        $links = $this->get_links();
7176
7177
        // Get all the student publications.
7178
        $works = $this->get_student_publications();
7179
7180
        // Get all the forums.
7181
        $forums = $this->get_forums(null, $course_code);
7182
7183
        // Get the final item form (see BT#11048) .
7184
        $finish = $this->getFinalItemForm();
7185
7186
        $headers = [
7187
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7188
            Display::return_icon('quiz.png', get_lang('Tests'), [], ICON_SIZE_BIG),
7189
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7190
            Display::return_icon('works.png', get_lang('Assignments'), [], ICON_SIZE_BIG),
7191
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7192
            Display::return_icon('add_learnpath_section.png', get_lang('Add section'), [], ICON_SIZE_BIG),
7193
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7194
        ];
7195
7196
        echo Display::return_message(get_lang('Click on the [Learner view] button to see your learning path'), 'normal');
7197
        $dir = $this->display_item_form('dir', get_lang('EnterDataAdd section'), 'add_item');
7198
7199
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
7200
7201
        echo Display::tabs(
7202
            $headers,
7203
            [
7204
                $documents,
7205
                $exercises,
7206
                $links,
7207
                $works,
7208
                $forums,
7209
                $dir,
7210
                $finish,
7211
            ],
7212
            'resource_tab',
7213
            [],
7214
            [],
7215
            $selected
7216
        );
7217
7218
        return true;
7219
    }
7220
7221
    /**
7222
     * Returns the extension of a document.
7223
     *
7224
     * @param string $filename
7225
     *
7226
     * @return string Extension (part after the last dot)
7227
     */
7228
    public function get_extension($filename)
7229
    {
7230
        $explode = explode('.', $filename);
7231
7232
        return $explode[count($explode) - 1];
7233
    }
7234
7235
    /**
7236
     * Displays a document by id.
7237
     *
7238
     * @param int  $id
7239
     * @param bool $show_title
7240
     * @param bool $iframe
7241
     * @param bool $edit_link
7242
     *
7243
     * @return string
7244
     */
7245
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7246
    {
7247
        $_course = api_get_course_info();
7248
        $course_id = api_get_course_int_id();
7249
        $id = (int) $id;
7250
        $return = '';
7251
        $table = Database::get_course_table(TABLE_DOCUMENT);
7252
        $sql_doc = "SELECT * FROM $table
7253
                    WHERE c_id = $course_id AND iid = $id";
7254
        $res_doc = Database::query($sql_doc);
7255
        $row_doc = Database::fetch_array($res_doc);
7256
7257
        // TODO: Add a path filter.
7258
        if ($iframe) {
7259
            $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>';
7260
        } else {
7261
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7262
        }
7263
7264
        return $return;
7265
    }
7266
7267
    /**
7268
     * Return HTML form to add/edit a quiz.
7269
     *
7270
     * @param string $action     Action (add/edit)
7271
     * @param int    $id         Item ID if already exists
7272
     * @param mixed  $extra_info Extra information (quiz ID if integer)
7273
     *
7274
     * @throws Exception
7275
     *
7276
     * @return string HTML form
7277
     */
7278
    public function display_quiz_form($action = 'add', $id = 0, $extra_info = '')
7279
    {
7280
        $course_id = api_get_course_int_id();
7281
        $id = (int) $id;
7282
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7283
        $item_title = '';
7284
        $item_description = '';
7285
        if ($id != 0 && is_array($extra_info)) {
7286
            $item_title = $extra_info['title'];
7287
            $item_description = $extra_info['description'];
7288
        } elseif (is_numeric($extra_info)) {
7289
            $sql = "SELECT title, description
7290
                    FROM $tbl_quiz
7291
                    WHERE c_id = $course_id AND iid = ".$extra_info;
7292
7293
            $result = Database::query($sql);
7294
            $row = Database::fetch_array($result);
7295
            $item_title = $row['title'];
7296
            $item_description = $row['description'];
7297
        }
7298
        $item_title = Security::remove_XSS($item_title);
7299
        $item_description = Security::remove_XSS($item_description);
7300
7301
        $parent = 0;
7302
        if ($id != 0 && is_array($extra_info)) {
7303
            $parent = $extra_info['parent_item_id'];
7304
        }
7305
7306
        $arrLP = $this->getItemsForForm();
7307
        $this->tree_array($arrLP);
7308
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7309
        unset($this->arrMenu);
7310
7311
        $form = new FormValidator(
7312
            'quiz_form',
7313
            'POST',
7314
            $this->getCurrentBuildingModeURL()
7315
        );
7316
        $defaults = [];
7317
7318
        if ($action === 'add') {
7319
            $legend = get_lang('Adding a test to the course');
7320
        } elseif ($action === 'move') {
7321
            $legend = get_lang('Move the current test');
7322
        } else {
7323
            $legend = get_lang('Edit the current test');
7324
        }
7325
7326
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7327
            $legend .= Display::return_message(get_lang('Warning ! ! !').' ! '.get_lang('Warning ! ! !EditingDocument'));
7328
        }
7329
7330
        $form->addHeader($legend);
7331
7332
        if ($action != 'move') {
7333
            $this->setItemTitle($form);
7334
            $defaults['title'] = $item_title;
7335
        }
7336
7337
        // Select for Parent item, root or chapter
7338
        $selectParent = $form->addSelect(
7339
            'parent',
7340
            get_lang('Parent'),
7341
            [],
7342
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7343
        );
7344
        $selectParent->addOption($this->name, 0);
7345
7346
        $arrHide = [
7347
            $id,
7348
        ];
7349
        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...
7350
            if ($action != 'add') {
7351
                if (
7352
                    ($arrLP[$i]['item_type'] == 'dir') &&
7353
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7354
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7355
                ) {
7356
                    $selectParent->addOption(
7357
                        $arrLP[$i]['title'],
7358
                        $arrLP[$i]['id'],
7359
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7360
                    );
7361
7362
                    if ($parent == $arrLP[$i]['id']) {
7363
                        $selectParent->setSelected($arrLP[$i]['id']);
7364
                    }
7365
                } else {
7366
                    $arrHide[] = $arrLP[$i]['id'];
7367
                }
7368
            } else {
7369
                if ($arrLP[$i]['item_type'] == 'dir') {
7370
                    $selectParent->addOption(
7371
                        $arrLP[$i]['title'],
7372
                        $arrLP[$i]['id'],
7373
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7374
                    );
7375
7376
                    if ($parent == $arrLP[$i]['id']) {
7377
                        $selectParent->setSelected($arrLP[$i]['id']);
7378
                    }
7379
                }
7380
            }
7381
        }
7382
7383
        if (is_array($arrLP)) {
7384
            reset($arrLP);
7385
        }
7386
7387
        $selectPrevious = $form->addSelect(
7388
            'previous',
7389
            get_lang('Position'),
7390
            [],
7391
            ['id' => 'previous']
7392
        );
7393
        $selectPrevious->addOption(get_lang('First position'), 0);
7394
7395
        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...
7396
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7397
                $arrLP[$i]['id'] != $id
7398
            ) {
7399
                $selectPrevious->addOption(
7400
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7401
                    $arrLP[$i]['id']
7402
                );
7403
7404
                if (is_array($extra_info)) {
7405
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7406
                        $selectPrevious->setSelected($arrLP[$i]['id']);
7407
                    }
7408
                } elseif ($action == 'add') {
7409
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7410
                }
7411
            }
7412
        }
7413
7414
        if ($action != 'move') {
7415
            $arrHide = [];
7416
            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...
7417
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7418
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7419
                }
7420
            }
7421
        }
7422
7423
        if ($action === 'add') {
7424
            $form->addButtonSave(get_lang('Add test to course'), 'submit_button');
7425
        } else {
7426
            $form->addButtonSave(get_lang('Edit the current test'), 'submit_button');
7427
        }
7428
7429
        if ($action === 'move') {
7430
            $form->addHidden('title', $item_title);
7431
            $form->addHidden('description', $item_description);
7432
        }
7433
7434
        if (is_numeric($extra_info)) {
7435
            $form->addHidden('path', $extra_info);
7436
        } elseif (is_array($extra_info)) {
7437
            $form->addHidden('path', $extra_info['path']);
7438
        }
7439
7440
        $form->addHidden('type', TOOL_QUIZ);
7441
        $form->addHidden('post_time', time());
7442
        $form->setDefaults($defaults);
7443
7444
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7445
    }
7446
7447
    /**
7448
     * Addition of Hotpotatoes tests.
7449
     *
7450
     * @param string $action
7451
     * @param int    $id         Internal ID of the item
7452
     * @param string $extra_info
7453
     *
7454
     * @return string HTML structure to display the hotpotatoes addition formular
7455
     */
7456
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
7457
    {
7458
        $course_id = api_get_course_int_id();
7459
        $uploadPath = DIR_HOTPOTATOES;
7460
7461
        if ($id != 0 && is_array($extra_info)) {
7462
            $item_title = stripslashes($extra_info['title']);
7463
            $item_description = stripslashes($extra_info['description']);
7464
        } elseif (is_numeric($extra_info)) {
7465
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
7466
7467
            $sql = "SELECT * FROM $TBL_DOCUMENT
7468
                    WHERE
7469
                        c_id = $course_id AND
7470
                        path LIKE '".$uploadPath."/%/%htm%' AND
7471
                        iid = ".(int) $extra_info."
7472
                    ORDER BY iid ASC";
7473
7474
            $res_hot = Database::query($sql);
7475
            $row = Database::fetch_array($res_hot);
7476
7477
            $item_title = $row['title'];
7478
            $item_description = $row['description'];
7479
7480
            if (!empty($row['comment'])) {
7481
                $item_title = $row['comment'];
7482
            }
7483
        } else {
7484
            $item_title = '';
7485
            $item_description = '';
7486
        }
7487
7488
        $parent = 0;
7489
        if ($id != 0 && is_array($extra_info)) {
7490
            $parent = $extra_info['parent_item_id'];
7491
        }
7492
7493
        $arrLP = $this->getItemsForForm();
7494
        $legend = '<legend>';
7495
        if ($action == 'add') {
7496
            $legend .= get_lang('Adding a test to the course');
7497
        } elseif ($action == 'move') {
7498
            $legend .= get_lang('Move the current test');
7499
        } else {
7500
            $legend .= get_lang('Edit the current test');
7501
        }
7502
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7503
            $legend .= Display:: return_message(
7504
                get_lang('Warning ! ! !').' ! '.get_lang('Warning ! ! !EditingDocument')
7505
            );
7506
        }
7507
        $legend .= '</legend>';
7508
7509
        $return = '<form method="POST">';
7510
        $return .= $legend;
7511
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
7512
        $return .= '<tr>';
7513
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
7514
        $return .= '<td class="input">';
7515
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
7516
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
7517
        $arrHide = [$id];
7518
7519
        if (count($arrLP) > 0) {
7520
            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...
7521
                if ($action != 'add') {
7522
                    if ($arrLP[$i]['item_type'] == 'dir' &&
7523
                        !in_array($arrLP[$i]['id'], $arrHide) &&
7524
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7525
                    ) {
7526
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7527
                    } else {
7528
                        $arrHide[] = $arrLP[$i]['id'];
7529
                    }
7530
                } else {
7531
                    if ($arrLP[$i]['item_type'] == 'dir') {
7532
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7533
                    }
7534
                }
7535
            }
7536
            reset($arrLP);
7537
        }
7538
7539
        $return .= '</select>';
7540
        $return .= '</td>';
7541
        $return .= '</tr>';
7542
        $return .= '<tr>';
7543
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
7544
        $return .= '<td class="input">';
7545
        $return .= '<select id="previous" name="previous" size="1">';
7546
        $return .= '<option class="top" value="0">'.get_lang('First position').'</option>';
7547
7548
        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...
7549
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7550
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7551
                    $selected = 'selected="selected" ';
7552
                } elseif ($action == 'add') {
7553
                    $selected = 'selected="selected" ';
7554
                } else {
7555
                    $selected = '';
7556
                }
7557
7558
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.
7559
                    get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
7560
            }
7561
        }
7562
7563
        $return .= '</select>';
7564
        $return .= '</td>';
7565
        $return .= '</tr>';
7566
7567
        if ($action != 'move') {
7568
            $return .= '<tr>';
7569
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
7570
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
7571
            $return .= '</tr>';
7572
            $id_prerequisite = 0;
7573
            if (is_array($arrLP) && count($arrLP) > 0) {
7574
                foreach ($arrLP as $key => $value) {
7575
                    if ($value['id'] == $id) {
7576
                        $id_prerequisite = $value['prerequisite'];
7577
                        break;
7578
                    }
7579
                }
7580
7581
                $arrHide = [];
7582
                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...
7583
                    if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7584
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7585
                    }
7586
                }
7587
            }
7588
        }
7589
7590
        $return .= '<tr>';
7591
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.
7592
            get_lang('Save hotpotatoes').'</button></td>';
7593
        $return .= '</tr>';
7594
        $return .= '</table>';
7595
7596
        if ($action == 'move') {
7597
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
7598
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
7599
        }
7600
7601
        if (is_numeric($extra_info)) {
7602
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
7603
        } elseif (is_array($extra_info)) {
7604
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
7605
        }
7606
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
7607
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
7608
        $return .= '</form>';
7609
7610
        return $return;
7611
    }
7612
7613
    /**
7614
     * Return the form to display the forum edit/add option.
7615
     *
7616
     * @param string $action
7617
     * @param int    $id         ID of the lp_item if already exists
7618
     * @param string $extra_info
7619
     *
7620
     * @throws Exception
7621
     *
7622
     * @return string HTML form
7623
     */
7624
    public function display_forum_form($action = 'add', $id = 0, $extra_info = '')
7625
    {
7626
        $course_id = api_get_course_int_id();
7627
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
7628
7629
        $item_title = '';
7630
        $item_description = '';
7631
7632
        if ($id != 0 && is_array($extra_info)) {
7633
            $item_title = stripslashes($extra_info['title']);
7634
        } elseif (is_numeric($extra_info)) {
7635
            $sql = "SELECT forum_title as title, forum_comment as comment
7636
                    FROM $tbl_forum
7637
                    WHERE c_id = $course_id AND forum_id = ".$extra_info;
7638
7639
            $result = Database::query($sql);
7640
            $row = Database::fetch_array($result);
7641
7642
            $item_title = $row['title'];
7643
            $item_description = $row['comment'];
7644
        }
7645
        $parent = 0;
7646
        if ($id != 0 && is_array($extra_info)) {
7647
            $parent = $extra_info['parent_item_id'];
7648
        }
7649
        $arrLP = $this->getItemsForForm();
7650
        $this->tree_array($arrLP);
7651
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7652
        unset($this->arrMenu);
7653
7654
        if ($action == 'add') {
7655
            $legend = get_lang('Adding a forum to the course');
7656
        } elseif ($action == 'move') {
7657
            $legend = get_lang('Move the current forum');
7658
        } else {
7659
            $legend = get_lang('Edit the current forum');
7660
        }
7661
7662
        $form = new FormValidator(
7663
            'forum_form',
7664
            'POST',
7665
            $this->getCurrentBuildingModeURL()
7666
        );
7667
        $defaults = [];
7668
7669
        $form->addHeader($legend);
7670
7671
        if ($action != 'move') {
7672
            $this->setItemTitle($form);
7673
            $defaults['title'] = $item_title;
7674
        }
7675
7676
        $selectParent = $form->addSelect(
7677
            'parent',
7678
            get_lang('Parent'),
7679
            [],
7680
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
7681
        );
7682
        $selectParent->addOption($this->name, 0);
7683
        $arrHide = [
7684
            $id,
7685
        ];
7686
        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...
7687
            if ($action != 'add') {
7688
                if ($arrLP[$i]['item_type'] == 'dir' &&
7689
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7690
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7691
                ) {
7692
                    $selectParent->addOption(
7693
                        $arrLP[$i]['title'],
7694
                        $arrLP[$i]['id'],
7695
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7696
                    );
7697
7698
                    if ($parent == $arrLP[$i]['id']) {
7699
                        $selectParent->setSelected($arrLP[$i]['id']);
7700
                    }
7701
                } else {
7702
                    $arrHide[] = $arrLP[$i]['id'];
7703
                }
7704
            } else {
7705
                if ($arrLP[$i]['item_type'] == 'dir') {
7706
                    $selectParent->addOption(
7707
                        $arrLP[$i]['title'],
7708
                        $arrLP[$i]['id'],
7709
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7710
                    );
7711
7712
                    if ($parent == $arrLP[$i]['id']) {
7713
                        $selectParent->setSelected($arrLP[$i]['id']);
7714
                    }
7715
                }
7716
            }
7717
        }
7718
7719
        if (is_array($arrLP)) {
7720
            reset($arrLP);
7721
        }
7722
7723
        $selectPrevious = $form->addSelect(
7724
            'previous',
7725
            get_lang('Position'),
7726
            [],
7727
            ['id' => 'previous', 'class' => 'learnpath_item_form']
7728
        );
7729
        $selectPrevious->addOption(get_lang('First position'), 0);
7730
7731
        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...
7732
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7733
                $arrLP[$i]['id'] != $id
7734
            ) {
7735
                $selectPrevious->addOption(
7736
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7737
                    $arrLP[$i]['id']
7738
                );
7739
7740
                if (isset($extra_info['previous_item_id']) &&
7741
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
7742
                ) {
7743
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7744
                } elseif ($action == 'add') {
7745
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7746
                }
7747
            }
7748
        }
7749
7750
        if ($action != 'move') {
7751
            $id_prerequisite = 0;
7752
            if (is_array($arrLP)) {
7753
                foreach ($arrLP as $key => $value) {
7754
                    if ($value['id'] == $id) {
7755
                        $id_prerequisite = $value['prerequisite'];
7756
                        break;
7757
                    }
7758
                }
7759
            }
7760
7761
            $arrHide = [];
7762
            for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

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

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

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
7763
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7764
                    if (isset($extra_info['previous_item_id']) &&
7765
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
7766
                    ) {
7767
                        $s_selected_position = $arrLP[$i]['id'];
7768
                    } elseif ($action == 'add') {
7769
                        $s_selected_position = 0;
7770
                    }
7771
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7772
                }
7773
            }
7774
        }
7775
7776
        if ($action == 'add') {
7777
            $form->addButtonSave(get_lang('Add forum to course'), 'submit_button');
7778
        } else {
7779
            $form->addButtonSave(get_lang('Edit the current forum'), 'submit_button');
7780
        }
7781
7782
        if ($action == 'move') {
7783
            $form->addHidden('title', $item_title);
7784
            $form->addHidden('description', $item_description);
7785
        }
7786
7787
        if (is_numeric($extra_info)) {
7788
            $form->addHidden('path', $extra_info);
7789
        } elseif (is_array($extra_info)) {
7790
            $form->addHidden('path', $extra_info['path']);
7791
        }
7792
        $form->addHidden('type', TOOL_FORUM);
7793
        $form->addHidden('post_time', time());
7794
        $form->setDefaults($defaults);
7795
7796
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7797
    }
7798
7799
    /**
7800
     * Return HTML form to add/edit forum threads.
7801
     *
7802
     * @param string $action
7803
     * @param int    $id         Item ID if already exists in learning path
7804
     * @param string $extra_info
7805
     *
7806
     * @throws Exception
7807
     *
7808
     * @return string HTML form
7809
     */
7810
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
7811
    {
7812
        $course_id = api_get_course_int_id();
7813
        if (empty($course_id)) {
7814
            return null;
7815
        }
7816
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
7817
7818
        $item_title = '';
7819
        $item_description = '';
7820
        if ($id != 0 && is_array($extra_info)) {
7821
            $item_title = stripslashes($extra_info['title']);
7822
        } elseif (is_numeric($extra_info)) {
7823
            $sql = "SELECT thread_title as title FROM $tbl_forum
7824
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
7825
7826
            $result = Database::query($sql);
7827
            $row = Database::fetch_array($result);
7828
7829
            $item_title = $row['title'];
7830
            $item_description = '';
7831
        }
7832
7833
        $parent = 0;
7834
        if ($id != 0 && is_array($extra_info)) {
7835
            $parent = $extra_info['parent_item_id'];
7836
        }
7837
7838
        $arrLP = $this->getItemsForForm();
7839
        $this->tree_array($arrLP);
7840
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7841
        unset($this->arrMenu);
7842
7843
        $form = new FormValidator(
7844
            'thread_form',
7845
            'POST',
7846
            $this->getCurrentBuildingModeURL()
7847
        );
7848
        $defaults = [];
7849
7850
        if ($action == 'add') {
7851
            $legend = get_lang('Adding a forum to the course');
7852
        } elseif ($action == 'move') {
7853
            $legend = get_lang('Move the current forum');
7854
        } else {
7855
            $legend = get_lang('Edit the current forum');
7856
        }
7857
7858
        $form->addHeader($legend);
7859
        $selectParent = $form->addSelect(
7860
            'parent',
7861
            get_lang('Parent'),
7862
            [],
7863
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7864
        );
7865
        $selectParent->addOption($this->name, 0);
7866
7867
        $arrHide = [
7868
            $id,
7869
        ];
7870
7871
        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...
7872
            if ($action != 'add') {
7873
                if (
7874
                    ($arrLP[$i]['item_type'] == 'dir') &&
7875
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7876
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7877
                ) {
7878
                    $selectParent->addOption(
7879
                        $arrLP[$i]['title'],
7880
                        $arrLP[$i]['id'],
7881
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7882
                    );
7883
7884
                    if ($parent == $arrLP[$i]['id']) {
7885
                        $selectParent->setSelected($arrLP[$i]['id']);
7886
                    }
7887
                } else {
7888
                    $arrHide[] = $arrLP[$i]['id'];
7889
                }
7890
            } else {
7891
                if ($arrLP[$i]['item_type'] == 'dir') {
7892
                    $selectParent->addOption(
7893
                        $arrLP[$i]['title'],
7894
                        $arrLP[$i]['id'],
7895
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7896
                    );
7897
7898
                    if ($parent == $arrLP[$i]['id']) {
7899
                        $selectParent->setSelected($arrLP[$i]['id']);
7900
                    }
7901
                }
7902
            }
7903
        }
7904
7905
        if ($arrLP != null) {
7906
            reset($arrLP);
7907
        }
7908
7909
        $selectPrevious = $form->addSelect(
7910
            'previous',
7911
            get_lang('Position'),
7912
            [],
7913
            ['id' => 'previous']
7914
        );
7915
        $selectPrevious->addOption(get_lang('First position'), 0);
7916
7917
        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...
7918
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7919
                $selectPrevious->addOption(
7920
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7921
                    $arrLP[$i]['id']
7922
                );
7923
7924
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7925
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7926
                } elseif ($action == 'add') {
7927
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7928
                }
7929
            }
7930
        }
7931
7932
        if ($action != 'move') {
7933
            $this->setItemTitle($form);
7934
            $defaults['title'] = $item_title;
7935
7936
            $id_prerequisite = 0;
7937
            if ($arrLP != null) {
7938
                foreach ($arrLP as $key => $value) {
7939
                    if ($value['id'] == $id) {
7940
                        $id_prerequisite = $value['prerequisite'];
7941
                        break;
7942
                    }
7943
                }
7944
            }
7945
7946
            $arrHide = [];
7947
            $s_selected_position = 0;
7948
            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...
7949
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7950
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7951
                        $s_selected_position = $arrLP[$i]['id'];
7952
                    } elseif ($action == 'add') {
7953
                        $s_selected_position = 0;
7954
                    }
7955
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7956
                }
7957
            }
7958
7959
            $selectPrerequisites = $form->addSelect(
7960
                'prerequisites',
7961
                get_lang('Prerequisites'),
7962
                [],
7963
                ['id' => 'prerequisites']
7964
            );
7965
            $selectPrerequisites->addOption(get_lang('No prerequisites'), 0);
7966
7967
            foreach ($arrHide as $key => $value) {
7968
                $selectPrerequisites->addOption($value['value'], $key);
7969
7970
                if ($key == $s_selected_position && $action == 'add') {
7971
                    $selectPrerequisites->setSelected($key);
7972
                } elseif ($key == $id_prerequisite && $action == 'edit') {
7973
                    $selectPrerequisites->setSelected($key);
7974
                }
7975
            }
7976
        }
7977
7978
        $form->addButtonSave(get_lang('Validate'), 'submit_button');
7979
7980
        if ($action == 'move') {
7981
            $form->addHidden('title', $item_title);
7982
            $form->addHidden('description', $item_description);
7983
        }
7984
7985
        if (is_numeric($extra_info)) {
7986
            $form->addHidden('path', $extra_info);
7987
        } elseif (is_array($extra_info)) {
7988
            $form->addHidden('path', $extra_info['path']);
7989
        }
7990
7991
        $form->addHidden('type', TOOL_THREAD);
7992
        $form->addHidden('post_time', time());
7993
        $form->setDefaults($defaults);
7994
7995
        return $form->returnForm();
7996
    }
7997
7998
    /**
7999
     * Return the HTML form to display an item (generally a dir item).
8000
     *
8001
     * @param string $item_type
8002
     * @param string $title
8003
     * @param string $action
8004
     * @param int    $id
8005
     * @param string $extra_info
8006
     *
8007
     * @throws Exception
8008
     * @throws HTML_QuickForm_Error
8009
     *
8010
     * @return string HTML form
8011
     */
8012
    public function display_item_form(
8013
        $item_type,
8014
        $title = '',
8015
        $action = 'add_item',
8016
        $id = 0,
8017
        $extra_info = 'new'
8018
    ) {
8019
        $_course = api_get_course_info();
8020
8021
        global $charset;
8022
8023
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8024
        $item_title = '';
8025
        $item_description = '';
8026
        $item_path_fck = '';
8027
8028
        if ($id != 0 && is_array($extra_info)) {
8029
            $item_title = $extra_info['title'];
8030
            $item_description = $extra_info['description'];
8031
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8032
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8033
        }
8034
        $parent = 0;
8035
        if ($id != 0 && is_array($extra_info)) {
8036
            $parent = $extra_info['parent_item_id'];
8037
        }
8038
8039
        $id = (int) $id;
8040
        $sql = "SELECT * FROM $tbl_lp_item
8041
                WHERE
8042
                    lp_id = ".$this->lp_id." AND
8043
                    iid != $id";
8044
8045
        if ($item_type == 'dir') {
8046
            $sql .= " AND parent_item_id = 0";
8047
        }
8048
8049
        $result = Database::query($sql);
8050
        $arrLP = [];
8051
        while ($row = Database::fetch_array($result)) {
8052
            $arrLP[] = [
8053
                'id' => $row['iid'],
8054
                'item_type' => $row['item_type'],
8055
                'title' => $this->cleanItemTitle($row['title']),
8056
                'title_raw' => $row['title'],
8057
                'path' => $row['path'],
8058
                'description' => $row['description'],
8059
                'parent_item_id' => $row['parent_item_id'],
8060
                'previous_item_id' => $row['previous_item_id'],
8061
                'next_item_id' => $row['next_item_id'],
8062
                'max_score' => $row['max_score'],
8063
                'min_score' => $row['min_score'],
8064
                'mastery_score' => $row['mastery_score'],
8065
                'prerequisite' => $row['prerequisite'],
8066
                'display_order' => $row['display_order'],
8067
            ];
8068
        }
8069
8070
        $this->tree_array($arrLP);
8071
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8072
        unset($this->arrMenu);
8073
8074
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8075
8076
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
8077
        $defaults['title'] = api_html_entity_decode(
8078
            $item_title,
8079
            ENT_QUOTES,
8080
            $charset
8081
        );
8082
        $defaults['description'] = $item_description;
8083
8084
        $form->addHeader($title);
8085
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
8086
        $arrHide[0]['padding'] = 20;
8087
        $charset = api_get_system_encoding();
8088
        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...
8089
            if ($action != 'add') {
8090
                if ($arrLP[$i]['item_type'] === 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8091
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8092
                ) {
8093
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8094
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8095
                    if ($parent == $arrLP[$i]['id']) {
8096
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8097
                    }
8098
                }
8099
            } else {
8100
                if ($arrLP[$i]['item_type'] === 'dir') {
8101
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8102
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8103
                    if ($parent == $arrLP[$i]['id']) {
8104
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8105
                    }
8106
                }
8107
            }
8108
        }
8109
8110
        if ($action != 'move') {
8111
            $this->setItemTitle($form);
8112
        } else {
8113
            $form->addElement('hidden', 'title');
8114
        }
8115
8116
        $parentSelect = $form->addElement(
8117
            'select',
8118
            'parent',
8119
            get_lang('Parent'),
8120
            '',
8121
            [
8122
                'id' => 'idParent',
8123
                'onchange' => 'javascript: load_cbo(this.value);',
8124
            ]
8125
        );
8126
8127
        foreach ($arrHide as $key => $value) {
8128
            $parentSelect->addOption(
8129
                $value['value'],
8130
                $key,
8131
                'style="padding-left:'.$value['padding'].'px;"'
8132
            );
8133
            $lastPosition = $key;
8134
        }
8135
8136
        if (!empty($s_selected_parent)) {
8137
            $parentSelect->setSelected($s_selected_parent);
8138
        }
8139
8140
        if (is_array($arrLP)) {
8141
            reset($arrLP);
8142
        }
8143
8144
        $arrHide = [];
8145
        // POSITION
8146
        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...
8147
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id &&
8148
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
8149
                //this is the same!
8150
                if (isset($extra_info['previous_item_id']) &&
8151
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8152
                ) {
8153
                    $s_selected_position = $arrLP[$i]['id'];
8154
                } elseif ($action == 'add') {
8155
                    $s_selected_position = $arrLP[$i]['id'];
8156
                }
8157
8158
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8159
            }
8160
        }
8161
8162
        $position = $form->addElement(
8163
            'select',
8164
            'previous',
8165
            get_lang('Position'),
8166
            '',
8167
            ['id' => 'previous']
8168
        );
8169
        $padding = isset($value['padding']) ? $value['padding'] : 0;
8170
        $position->addOption(get_lang('First position'), 0, 'style="padding-left:'.$padding.'px;"');
8171
8172
        $lastPosition = null;
8173
        foreach ($arrHide as $key => $value) {
8174
            $position->addOption($value['value'], $key, 'style="padding-left:'.$padding.'px;"');
8175
            $lastPosition = $key;
8176
        }
8177
8178
        if (!empty($s_selected_position)) {
8179
            $position->setSelected($s_selected_position);
8180
        }
8181
8182
        // When new chapter add at the end
8183
        if ($action === 'add_item') {
8184
            $position->setSelected($lastPosition);
8185
        }
8186
8187
        if (is_array($arrLP)) {
8188
            reset($arrLP);
8189
        }
8190
8191
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
8192
8193
        //fix in order to use the tab
8194
        if ($item_type === 'dir') {
8195
            $form->addElement('hidden', 'type', 'dir');
8196
        }
8197
8198
        $extension = null;
8199
        if (!empty($item_path)) {
8200
            $extension = pathinfo($item_path, PATHINFO_EXTENSION);
8201
        }
8202
8203
        //assets can't be modified
8204
        //$item_type == 'asset' ||
8205
        if (($item_type == 'sco') && ($extension == 'html' || $extension == 'htm')) {
8206
            if ($item_type == 'sco') {
8207
                $form->addElement(
8208
                    'html',
8209
                    '<script>alert("'.get_lang('Warning ! ! !WhenEditingScorm').'")</script>'
8210
                );
8211
            }
8212
            $renderer = $form->defaultRenderer();
8213
            $renderer->setElementTemplate(
8214
                '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
8215
                'content_lp'
8216
            );
8217
8218
            $relative_prefix = '';
8219
            $editor_config = [
8220
                'ToolbarSet' => 'LearningPathDocuments',
8221
                'Width' => '100%',
8222
                'Height' => '500',
8223
                'FullPage' => true,
8224
                'CreateDocumentDir' => $relative_prefix,
8225
                'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
8226
                'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
8227
            ];
8228
8229
            $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
8230
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
8231
            $defaults['content_lp'] = file_get_contents($content_path);
8232
        }
8233
8234
        if (!empty($id)) {
8235
            $form->addHidden('id', $id);
8236
        }
8237
8238
        $form->addElement('hidden', 'type', $item_type);
8239
        $form->addElement('hidden', 'post_time', time());
8240
        $form->setDefaults($defaults);
8241
8242
        return $form->returnForm();
8243
    }
8244
8245
    /**
8246
     * @return string
8247
     */
8248
    public function getCurrentBuildingModeURL()
8249
    {
8250
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
8251
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
8252
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
8253
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
8254
8255
        $currentUrl = api_get_self().'?'.api_get_cidreq().
8256
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
8257
8258
        return $currentUrl;
8259
    }
8260
8261
    /**
8262
     * Returns the form to update or create a document.
8263
     *
8264
     * @param string $action     (add/edit)
8265
     * @param int    $id         ID of the lp_item (if already exists)
8266
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8267
     *
8268
     * @throws Exception
8269
     * @throws HTML_QuickForm_Error
8270
     *
8271
     * @return string HTML form
8272
     */
8273
    public function display_document_form($action = 'add', $id = 0, $extra_info = 'new')
8274
    {
8275
        $course_id = api_get_course_int_id();
8276
        $_course = api_get_course_info();
8277
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8278
8279
        $no_display_edit_textarea = false;
8280
        $item_description = '';
8281
        //If action==edit document
8282
        //We don't display the document form if it's not an editable document (html or txt file)
8283
        if ($action === 'edit') {
8284
            if (is_array($extra_info)) {
8285
                $path_parts = pathinfo($extra_info['dir']);
8286
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8287
                    $no_display_edit_textarea = true;
8288
                }
8289
            }
8290
        }
8291
        $no_display_add = false;
8292
8293
        // If action==add an existing document
8294
        // We don't display the document form if it's not an editable document (html or txt file).
8295
        if ($action === 'add') {
8296
            if (is_numeric($extra_info)) {
8297
                $extra_info = (int) $extra_info;
8298
                $sql_doc = "SELECT path FROM $tbl_doc
8299
                            WHERE c_id = $course_id AND iid = ".$extra_info;
8300
                $result = Database::query($sql_doc);
8301
                $path_file = Database::result($result, 0, 0);
8302
                $path_parts = pathinfo($path_file);
8303
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8304
                    $no_display_add = true;
8305
                }
8306
            }
8307
        }
8308
8309
        $item_title = '';
8310
        $item_description = '';
8311
        if ($id != 0 && is_array($extra_info)) {
8312
            $item_title = stripslashes($extra_info['title']);
8313
            $item_description = stripslashes($extra_info['description']);
8314
            if (empty($item_title)) {
8315
                $path_parts = pathinfo($extra_info['path']);
8316
                $item_title = stripslashes($path_parts['filename']);
8317
            }
8318
        } elseif (is_numeric($extra_info)) {
8319
            $sql = "SELECT path, title FROM $tbl_doc
8320
                    WHERE
8321
                        c_id = ".$course_id." AND
8322
                        iid = ".intval($extra_info);
8323
            $result = Database::query($sql);
8324
            $row = Database::fetch_array($result);
8325
            $item_title = $row['title'];
8326
            $item_title = str_replace('_', ' ', $item_title);
8327
            if (empty($item_title)) {
8328
                $path_parts = pathinfo($row['path']);
8329
                $item_title = stripslashes($path_parts['filename']);
8330
            }
8331
        }
8332
8333
        $return = '<legend>';
8334
        $parent = 0;
8335
        if ($id != 0 && is_array($extra_info)) {
8336
            $parent = $extra_info['parent_item_id'];
8337
        }
8338
8339
        $arrLP = $this->getItemsForForm();
8340
        $this->tree_array($arrLP);
8341
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8342
        unset($this->arrMenu);
8343
8344
        if ($action == 'add') {
8345
            $return .= get_lang('Create a new document');
8346
        } elseif ($action == 'move') {
8347
            $return .= get_lang('Move the current document');
8348
        } else {
8349
            $return .= get_lang('Edit the current document');
8350
        }
8351
        $return .= '</legend>';
8352
8353
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
8354
            $return .= Display::return_message(
8355
                '<strong>'.get_lang('Warning ! ! !').' !</strong><br />'.get_lang('Warning ! ! !EditingDocument'),
8356
                false
8357
            );
8358
        }
8359
        $form = new FormValidator(
8360
            'form',
8361
            'POST',
8362
            $this->getCurrentBuildingModeURL(),
8363
            '',
8364
            ['enctype' => 'multipart/form-data']
8365
        );
8366
        $defaults['title'] = Security::remove_XSS($item_title);
8367
        if (empty($item_title)) {
8368
            $defaults['title'] = Security::remove_XSS($item_title);
8369
        }
8370
        $defaults['description'] = $item_description;
8371
        $form->addElement('html', $return);
8372
8373
        if ($action != 'move') {
8374
            $data = $this->generate_lp_folder($_course);
8375
            if ($action != 'edit') {
8376
                $folders = DocumentManager::get_all_document_folders(
8377
                    $_course,
8378
                    0,
8379
                    true
8380
                );
8381
                DocumentManager::build_directory_selector(
8382
                    $folders,
8383
                    '',
8384
                    [],
8385
                    true,
8386
                    $form,
8387
                    'directory_parent_id'
8388
                );
8389
            }
8390
8391
            if (isset($data['id'])) {
8392
                $defaults['directory_parent_id'] = $data['id'];
8393
            }
8394
            $this->setItemTitle($form);
8395
        }
8396
8397
        $arrHide[0]['value'] = $this->name;
8398
        $arrHide[0]['padding'] = 20;
8399
8400
        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...
8401
            if ($action != 'add') {
8402
                if ($arrLP[$i]['item_type'] == 'dir' &&
8403
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8404
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8405
                ) {
8406
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8407
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8408
                }
8409
            } else {
8410
                if ($arrLP[$i]['item_type'] == 'dir') {
8411
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8412
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8413
                }
8414
            }
8415
        }
8416
8417
        $parentSelect = $form->addSelect(
8418
            'parent',
8419
            get_lang('Parent'),
8420
            [],
8421
            [
8422
                'id' => 'idParent',
8423
                'onchange' => 'javascript: load_cbo(this.value);',
8424
            ]
8425
        );
8426
8427
        $my_count = 0;
8428
        foreach ($arrHide as $key => $value) {
8429
            if ($my_count != 0) {
8430
                // The LP name is also the first section and is not in the same charset like the other sections.
8431
                $value['value'] = Security::remove_XSS($value['value']);
8432
                $parentSelect->addOption(
8433
                    $value['value'],
8434
                    $key,
8435
                    'style="padding-left:'.$value['padding'].'px;"'
8436
                );
8437
            } else {
8438
                $value['value'] = Security::remove_XSS($value['value']);
8439
                $parentSelect->addOption(
8440
                    $value['value'],
8441
                    $key,
8442
                    'style="padding-left:'.$value['padding'].'px;"'
8443
                );
8444
            }
8445
            $my_count++;
8446
        }
8447
8448
        if (!empty($id)) {
8449
            $parentSelect->setSelected($parent);
8450
        } else {
8451
            $parent_item_id = Session::read('parent_item_id', 0);
8452
            $parentSelect->setSelected($parent_item_id);
8453
        }
8454
8455
        if (is_array($arrLP)) {
8456
            reset($arrLP);
8457
        }
8458
8459
        $arrHide = [];
8460
        // POSITION
8461
        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...
8462
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
8463
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
8464
            ) {
8465
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8466
            }
8467
        }
8468
8469
        $selectedPosition = isset($extra_info['previous_item_id']) ? $extra_info['previous_item_id'] : 0;
8470
8471
        $position = $form->addSelect(
8472
            'previous',
8473
            get_lang('Position'),
8474
            [],
8475
            ['id' => 'previous']
8476
        );
8477
8478
        $position->addOption(get_lang('First position'), 0);
8479
        foreach ($arrHide as $key => $value) {
8480
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8481
            $position->addOption(
8482
                $value['value'],
8483
                $key,
8484
                'style="padding-left:'.$padding.'px;"'
8485
            );
8486
        }
8487
8488
        $position->setSelected($selectedPosition);
8489
8490
        if (is_array($arrLP)) {
8491
            reset($arrLP);
8492
        }
8493
8494
        if ($action != 'move') {
8495
            $arrHide = [];
8496
            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...
8497
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
8498
                    $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8499
                ) {
8500
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8501
                }
8502
            }
8503
8504
            if (!$no_display_add) {
8505
                $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8506
                $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8507
                if ($extra_info == 'new' || $item_type == TOOL_DOCUMENT ||
8508
                    $item_type == TOOL_LP_FINAL_ITEM || $edit == 'true'
8509
                ) {
8510
                    if (isset($_POST['content'])) {
8511
                        $content = stripslashes($_POST['content']);
8512
                    } elseif (is_array($extra_info)) {
8513
                        //If it's an html document or a text file
8514
                        if (!$no_display_edit_textarea) {
8515
                            $content = $this->display_document(
8516
                                $extra_info['path'],
8517
                                false,
8518
                                false
8519
                            );
8520
                        }
8521
                    } elseif (is_numeric($extra_info)) {
8522
                        $content = $this->display_document(
8523
                            $extra_info,
8524
                            false,
8525
                            false
8526
                        );
8527
                    } else {
8528
                        $content = '';
8529
                    }
8530
8531
                    if (!$no_display_edit_textarea) {
8532
                        // We need to calculate here some specific settings for the online editor.
8533
                        // The calculated settings work for documents in the Documents tool
8534
                        // (on the root or in subfolders).
8535
                        // For documents in native scorm packages it is unclear whether the
8536
                        // online editor should be activated or not.
8537
8538
                        // A new document, it is in the root of the repository.
8539
                        $relative_path = '';
8540
                        $relative_prefix = '';
8541
                        if (is_array($extra_info) && $extra_info != 'new') {
8542
                            // The document already exists. Whe have to determine its relative path towards the repository root.
8543
                            $relative_path = explode('/', $extra_info['dir']);
8544
                            $cnt = count($relative_path) - 2;
8545
                            if ($cnt < 0) {
8546
                                $cnt = 0;
8547
                            }
8548
                            $relative_prefix = str_repeat('../', $cnt);
8549
                            $relative_path = array_slice($relative_path, 1, $cnt);
8550
                            $relative_path = implode('/', $relative_path);
8551
                            if (strlen($relative_path) > 0) {
8552
                                $relative_path = $relative_path.'/';
8553
                            }
8554
                        } else {
8555
                            $result = $this->generate_lp_folder($_course);
8556
                            $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
8557
                            $relative_prefix = '../../';
8558
                        }
8559
8560
                        $editor_config = [
8561
                            'ToolbarSet' => 'LearningPathDocuments',
8562
                            'Width' => '100%',
8563
                            'Height' => '500',
8564
                            'FullPage' => true,
8565
                            'CreateDocumentDir' => $relative_prefix,
8566
                            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
8567
                            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
8568
                        ];
8569
8570
                        if ($_GET['action'] == 'add_item') {
8571
                            $class = 'add';
8572
                            $text = get_lang('Add this document to the course');
8573
                        } else {
8574
                            if ($_GET['action'] == 'edit_item') {
8575
                                $class = 'save';
8576
                                $text = get_lang('Save document');
8577
                            }
8578
                        }
8579
8580
                        $form->addButtonSave($text, 'submit_button');
8581
                        $renderer = $form->defaultRenderer();
8582
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
8583
                        $form->addElement('html', '<div class="editor-lp">');
8584
                        $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
8585
                        $form->addElement('html', '</div>');
8586
                        $defaults['content_lp'] = $content;
8587
                    }
8588
                } elseif (is_numeric($extra_info)) {
8589
                    $form->addButtonSave(get_lang('Save document'), 'submit_button');
8590
8591
                    $return = $this->display_document($extra_info, true, true, true);
8592
                    $form->addElement('html', $return);
8593
                }
8594
            }
8595
        }
8596
        if (isset($extra_info['item_type']) &&
8597
            $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
8598
        ) {
8599
            $parentSelect->freeze();
8600
            $position->freeze();
8601
        }
8602
8603
        if ($action == 'move') {
8604
            $form->addElement('hidden', 'title', $item_title);
8605
            $form->addElement('hidden', 'description', $item_description);
8606
        }
8607
        if (is_numeric($extra_info)) {
8608
            $form->addButtonSave(get_lang('Save document'), 'submit_button');
8609
            $form->addElement('hidden', 'path', $extra_info);
8610
        } elseif (is_array($extra_info)) {
8611
            $form->addButtonSave(get_lang('Save document'), 'submit_button');
8612
            $form->addElement('hidden', 'path', $extra_info['path']);
8613
        }
8614
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
8615
        $form->addElement('hidden', 'post_time', time());
8616
        $form->setDefaults($defaults);
8617
8618
        return $form->returnForm();
8619
    }
8620
8621
    /**
8622
     * Returns the form to update or create a read-out text.
8623
     *
8624
     * @param string $action     "add" or "edit"
8625
     * @param int    $id         ID of the lp_item (if already exists)
8626
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8627
     *
8628
     * @throws Exception
8629
     * @throws HTML_QuickForm_Error
8630
     *
8631
     * @return string HTML form
8632
     */
8633
    public function displayFrmReadOutText($action = 'add', $id = 0, $extra_info = 'new')
8634
    {
8635
        $course_id = api_get_course_int_id();
8636
        $_course = api_get_course_info();
8637
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8638
8639
        $no_display_edit_textarea = false;
8640
        $item_description = '';
8641
        //If action==edit document
8642
        //We don't display the document form if it's not an editable document (html or txt file)
8643
        if ($action == 'edit') {
8644
            if (is_array($extra_info)) {
8645
                $path_parts = pathinfo($extra_info['dir']);
8646
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
8647
                    $no_display_edit_textarea = true;
8648
                }
8649
            }
8650
        }
8651
        $no_display_add = false;
8652
8653
        $item_title = '';
8654
        $item_description = '';
8655
        if ($id != 0 && is_array($extra_info)) {
8656
            $item_title = stripslashes($extra_info['title']);
8657
            $item_description = stripslashes($extra_info['description']);
8658
            $item_terms = stripslashes($extra_info['terms']);
8659
            if (empty($item_title)) {
8660
                $path_parts = pathinfo($extra_info['path']);
8661
                $item_title = stripslashes($path_parts['filename']);
8662
            }
8663
        } elseif (is_numeric($extra_info)) {
8664
            $sql = "SELECT path, title FROM $tbl_doc WHERE c_id = ".$course_id." AND iid = ".intval($extra_info);
8665
            $result = Database::query($sql);
8666
            $row = Database::fetch_array($result);
8667
            $item_title = $row['title'];
8668
            $item_title = str_replace('_', ' ', $item_title);
8669
            if (empty($item_title)) {
8670
                $path_parts = pathinfo($row['path']);
8671
                $item_title = stripslashes($path_parts['filename']);
8672
            }
8673
        }
8674
8675
        $parent = 0;
8676
        if ($id != 0 && is_array($extra_info)) {
8677
            $parent = $extra_info['parent_item_id'];
8678
        }
8679
8680
        $arrLP = $this->getItemsForForm();
8681
        $this->tree_array($arrLP);
8682
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8683
        unset($this->arrMenu);
8684
8685
        if ($action === 'add') {
8686
            $formHeader = get_lang('Create a new document');
8687
        } else {
8688
            $formHeader = get_lang('Edit the current document');
8689
        }
8690
8691
        if ('edit' === $action) {
8692
            $urlAudioIcon = Display::url(
8693
                Display::return_icon('audio.png', get_lang('Create read-out text'), [], ICON_SIZE_TINY),
8694
                api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'&'
8695
                    .http_build_query(['view' => 'build', 'id' => $id, 'action' => 'add_audio'])
8696
            );
8697
        } else {
8698
            $urlAudioIcon = Display::return_icon('audio.png', get_lang('Create read-out text'), [], ICON_SIZE_TINY);
8699
        }
8700
8701
        $form = new FormValidator(
8702
            'frm_add_reading',
8703
            'POST',
8704
            $this->getCurrentBuildingModeURL(),
8705
            '',
8706
            ['enctype' => 'multipart/form-data']
8707
        );
8708
        $form->addHeader($formHeader);
8709
        $form->addHtml(
8710
            Display::return_message(
8711
                sprintf(get_lang('You need attach a audio file according to the text, clicking on the %s icon.'), $urlAudioIcon),
8712
                'normal',
8713
                false
8714
            )
8715
        );
8716
        $defaults['title'] = !empty($item_title) ? Security::remove_XSS($item_title) : '';
8717
        $defaults['description'] = $item_description;
8718
8719
        $data = $this->generate_lp_folder($_course);
8720
8721
        if ($action != 'edit') {
8722
            $folders = DocumentManager::get_all_document_folders($_course, 0, true);
8723
            DocumentManager::build_directory_selector(
8724
                $folders,
8725
                '',
8726
                [],
8727
                true,
8728
                $form,
8729
                'directory_parent_id'
8730
            );
8731
        }
8732
8733
        if (isset($data['id'])) {
8734
            $defaults['directory_parent_id'] = $data['id'];
8735
        }
8736
        $this->setItemTitle($form);
8737
8738
        $arrHide[0]['value'] = $this->name;
8739
        $arrHide[0]['padding'] = 20;
8740
8741
        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...
8742
            if ($action != 'add') {
8743
                if ($arrLP[$i]['item_type'] == 'dir' &&
8744
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8745
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8746
                ) {
8747
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8748
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8749
                }
8750
            } else {
8751
                if ($arrLP[$i]['item_type'] == 'dir') {
8752
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8753
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8754
                }
8755
            }
8756
        }
8757
8758
        $parent_select = $form->addSelect(
8759
            'parent',
8760
            get_lang('Parent'),
8761
            [],
8762
            ['onchange' => "javascript: load_cbo(this.value, 'frm_add_reading_previous');"]
8763
        );
8764
8765
        $my_count = 0;
8766
        foreach ($arrHide as $key => $value) {
8767
            if ($my_count != 0) {
8768
                // The LP name is also the first section and is not in the same charset like the other sections.
8769
                $value['value'] = Security::remove_XSS($value['value']);
8770
                $parent_select->addOption(
8771
                    $value['value'],
8772
                    $key,
8773
                    'style="padding-left:'.$value['padding'].'px;"'
8774
                );
8775
            } else {
8776
                $value['value'] = Security::remove_XSS($value['value']);
8777
                $parent_select->addOption(
8778
                    $value['value'],
8779
                    $key,
8780
                    'style="padding-left:'.$value['padding'].'px;"'
8781
                );
8782
            }
8783
            $my_count++;
8784
        }
8785
8786
        if (!empty($id)) {
8787
            $parent_select->setSelected($parent);
8788
        } else {
8789
            $parent_item_id = Session::read('parent_item_id', 0);
8790
            $parent_select->setSelected($parent_item_id);
8791
        }
8792
8793
        if (is_array($arrLP)) {
8794
            reset($arrLP);
8795
        }
8796
8797
        $arrHide = [];
8798
        $s_selected_position = null;
8799
8800
        // POSITION
8801
        $lastPosition = null;
8802
8803
        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...
8804
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) &&
8805
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8806
            ) {
8807
                if ((isset($extra_info['previous_item_id']) &&
8808
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
8809
                ) {
8810
                    $s_selected_position = $arrLP[$i]['id'];
8811
                }
8812
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8813
            }
8814
            $lastPosition = $arrLP[$i]['id'];
8815
        }
8816
8817
        if (empty($s_selected_position)) {
8818
            $s_selected_position = $lastPosition;
8819
        }
8820
8821
        $position = $form->addSelect(
8822
            'previous',
8823
            get_lang('Position'),
8824
            []
8825
        );
8826
        $position->addOption(get_lang('First position'), 0);
8827
8828
        foreach ($arrHide as $key => $value) {
8829
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8830
            $position->addOption(
8831
                $value['value'],
8832
                $key,
8833
                'style="padding-left:'.$padding.'px;"'
8834
            );
8835
        }
8836
        $position->setSelected($s_selected_position);
8837
8838
        if (is_array($arrLP)) {
8839
            reset($arrLP);
8840
        }
8841
8842
        $arrHide = [];
8843
8844
        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...
8845
            if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
8846
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8847
            ) {
8848
                $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8849
            }
8850
        }
8851
8852
        if (!$no_display_add) {
8853
            $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8854
            $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8855
8856
            if ($extra_info == 'new' || $item_type == TOOL_READOUT_TEXT || $edit == 'true') {
8857
                if (!$no_display_edit_textarea) {
8858
                    $content = '';
8859
8860
                    if (isset($_POST['content'])) {
8861
                        $content = stripslashes($_POST['content']);
8862
                    } elseif (is_array($extra_info)) {
8863
                        $content = $this->display_document($extra_info['path'], false, false);
8864
                    } elseif (is_numeric($extra_info)) {
8865
                        $content = $this->display_document($extra_info, false, false);
8866
                    }
8867
8868
                    // A new document, it is in the root of the repository.
8869
                    if (is_array($extra_info) && $extra_info != 'new') {
8870
                    } else {
8871
                        $this->generate_lp_folder($_course);
8872
                    }
8873
8874
                    if ($_GET['action'] == 'add_item') {
8875
                        $text = get_lang('Add this document to the course');
8876
                    } else {
8877
                        $text = get_lang('Save document');
8878
                    }
8879
8880
                    $form->addTextarea('content_lp', get_lang('Content'), ['rows' => 20]);
8881
                    $form
8882
                        ->defaultRenderer()
8883
                        ->setElementTemplate($form->getDefaultElementTemplate(), 'content_lp');
8884
                    $form->addButtonSave($text, 'submit_button');
8885
                    $defaults['content_lp'] = $content;
8886
                }
8887
            } elseif (is_numeric($extra_info)) {
8888
                $form->addButtonSave(get_lang('Save document'), 'submit_button');
8889
8890
                $return = $this->display_document($extra_info, true, true, true);
8891
                $form->addElement('html', $return);
8892
            }
8893
        }
8894
8895
        if (is_numeric($extra_info)) {
8896
            $form->addElement('hidden', 'path', $extra_info);
8897
        } elseif (is_array($extra_info)) {
8898
            $form->addElement('hidden', 'path', $extra_info['path']);
8899
        }
8900
8901
        $form->addElement('hidden', 'type', TOOL_READOUT_TEXT);
8902
        $form->addElement('hidden', 'post_time', time());
8903
        $form->setDefaults($defaults);
8904
8905
        return $form->returnForm();
8906
    }
8907
8908
    /**
8909
     * @param array  $courseInfo
8910
     * @param string $content
8911
     * @param string $title
8912
     * @param int    $parentId
8913
     *
8914
     * @throws \Doctrine\ORM\ORMException
8915
     * @throws \Doctrine\ORM\OptimisticLockException
8916
     * @throws \Doctrine\ORM\TransactionRequiredException
8917
     *
8918
     * @return int
8919
     */
8920
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
8921
    {
8922
        $creatorId = api_get_user_id();
8923
        $sessionId = api_get_session_id();
8924
8925
        // Generates folder
8926
        $result = $this->generate_lp_folder($courseInfo);
8927
        $dir = $result['dir'];
8928
8929
        if (empty($parentId) || $parentId == '/') {
8930
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
8931
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
8932
8933
            if ($parentId === '/') {
8934
                $dir = '/';
8935
            }
8936
8937
            // Please, do not modify this dirname formatting.
8938
            if (strstr($dir, '..')) {
8939
                $dir = '/';
8940
            }
8941
8942
            if (!empty($dir[0]) && $dir[0] == '.') {
8943
                $dir = substr($dir, 1);
8944
            }
8945
            if (!empty($dir[0]) && $dir[0] != '/') {
8946
                $dir = '/'.$dir;
8947
            }
8948
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
8949
                $dir .= '/';
8950
            }
8951
        } else {
8952
            $parentInfo = DocumentManager::get_document_data_by_id(
8953
                $parentId,
8954
                $courseInfo['code']
8955
            );
8956
            if (!empty($parentInfo)) {
8957
                $dir = $parentInfo['path'].'/';
8958
            }
8959
        }
8960
8961
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
8962
8963
        if (!is_dir($filepath)) {
8964
            $dir = '/';
8965
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
8966
        }
8967
8968
        $originalTitle = !empty($title) ? $title : $_POST['title'];
8969
8970
        if (!empty($title)) {
8971
            $title = api_replace_dangerous_char(stripslashes($title));
8972
        } else {
8973
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
8974
        }
8975
8976
        $title = disable_dangerous_file($title);
8977
        $filename = $title;
8978
        $content = !empty($content) ? $content : $_POST['content_lp'];
8979
        $tmpFileName = $filename;
8980
8981
        $i = 0;
8982
        while (file_exists($filepath.$tmpFileName.'.html')) {
8983
            $tmpFileName = $filename.'_'.++$i;
8984
        }
8985
8986
        $filename = $tmpFileName.'.html';
8987
        $content = stripslashes($content);
8988
8989
        if (file_exists($filepath.$filename)) {
8990
            return 0;
8991
        }
8992
8993
        $putContent = file_put_contents($filepath.$filename, $content);
8994
8995
        if ($putContent === false) {
8996
            return 0;
8997
        }
8998
8999
        $fileSize = filesize($filepath.$filename);
9000
        $saveFilePath = $dir.$filename;
9001
9002
        $document = DocumentManager::addDocument(
9003
            $courseInfo,
9004
            $saveFilePath,
9005
            'file',
9006
            $fileSize,
9007
            $tmpFileName,
9008
            '',
9009
            0, //readonly
9010
            true,
9011
            null,
9012
            $sessionId,
9013
            $creatorId
9014
        );
9015
9016
        $documentId = $document->getId();
9017
9018
        if (!$document) {
9019
            return 0;
9020
        }
9021
9022
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
9023
        $newTitle = $originalTitle;
9024
9025
        if ($newComment || $newTitle) {
9026
            $em = Database::getManager();
9027
9028
            if ($newComment) {
9029
                $document->setComment($newComment);
9030
            }
9031
9032
            if ($newTitle) {
9033
                $document->setTitle($newTitle);
9034
            }
9035
9036
            $em->persist($document);
9037
            $em->flush();
9038
        }
9039
9040
        return $documentId;
9041
    }
9042
9043
    /**
9044
     * Return HTML form to add/edit a link item.
9045
     *
9046
     * @param string $action     (add/edit)
9047
     * @param int    $id         Item ID if exists
9048
     * @param mixed  $extra_info
9049
     *
9050
     * @throws Exception
9051
     * @throws HTML_QuickForm_Error
9052
     *
9053
     * @return string HTML form
9054
     */
9055
    public function display_link_form($action = 'add', $id = 0, $extra_info = '')
9056
    {
9057
        $course_id = api_get_course_int_id();
9058
        $tbl_link = Database::get_course_table(TABLE_LINK);
9059
9060
        $item_title = '';
9061
        $item_description = '';
9062
        $item_url = '';
9063
9064
        if ($id != 0 && is_array($extra_info)) {
9065
            $item_title = stripslashes($extra_info['title']);
9066
            $item_description = stripslashes($extra_info['description']);
9067
            $item_url = stripslashes($extra_info['url']);
9068
        } elseif (is_numeric($extra_info)) {
9069
            $extra_info = (int) $extra_info;
9070
            $sql = "SELECT title, description, url
9071
                    FROM $tbl_link
9072
                    WHERE c_id = $course_id AND iid = $extra_info";
9073
            $result = Database::query($sql);
9074
            $row = Database::fetch_array($result);
9075
            $item_title = $row['title'];
9076
            $item_description = $row['description'];
9077
            $item_url = $row['url'];
9078
        }
9079
9080
        $form = new FormValidator(
9081
            'edit_link',
9082
            'POST',
9083
            $this->getCurrentBuildingModeURL()
9084
        );
9085
        $defaults = [];
9086
        $parent = 0;
9087
        if ($id != 0 && is_array($extra_info)) {
9088
            $parent = $extra_info['parent_item_id'];
9089
        }
9090
9091
        $arrLP = $this->getItemsForForm();
9092
9093
        $this->tree_array($arrLP);
9094
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9095
        unset($this->arrMenu);
9096
9097
        if ($action == 'add') {
9098
            $legend = get_lang('Adding a link to the course');
9099
        } elseif ($action == 'move') {
9100
            $legend = get_lang('Move the current link');
9101
        } else {
9102
            $legend = get_lang('Edit the current link');
9103
        }
9104
9105
        $form->addHeader($legend);
9106
9107
        if ($action != 'move') {
9108
            $this->setItemTitle($form);
9109
            $defaults['title'] = $item_title;
9110
        }
9111
9112
        $selectParent = $form->addSelect(
9113
            'parent',
9114
            get_lang('Parent'),
9115
            [],
9116
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
9117
        );
9118
        $selectParent->addOption($this->name, 0);
9119
        $arrHide = [
9120
            $id,
9121
        ];
9122
9123
        $parent_item_id = Session::read('parent_item_id', 0);
9124
9125
        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...
9126
            if ($action != 'add') {
9127
                if (
9128
                    ($arrLP[$i]['item_type'] == 'dir') &&
9129
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9130
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9131
                ) {
9132
                    $selectParent->addOption(
9133
                        $arrLP[$i]['title'],
9134
                        $arrLP[$i]['id'],
9135
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
9136
                    );
9137
9138
                    if ($parent == $arrLP[$i]['id']) {
9139
                        $selectParent->setSelected($arrLP[$i]['id']);
9140
                    }
9141
                } else {
9142
                    $arrHide[] = $arrLP[$i]['id'];
9143
                }
9144
            } else {
9145
                if ($arrLP[$i]['item_type'] == 'dir') {
9146
                    $selectParent->addOption(
9147
                        $arrLP[$i]['title'],
9148
                        $arrLP[$i]['id'],
9149
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
9150
                    );
9151
9152
                    if ($parent_item_id == $arrLP[$i]['id']) {
9153
                        $selectParent->setSelected($arrLP[$i]['id']);
9154
                    }
9155
                }
9156
            }
9157
        }
9158
9159
        if (is_array($arrLP)) {
9160
            reset($arrLP);
9161
        }
9162
9163
        $selectPrevious = $form->addSelect(
9164
            'previous',
9165
            get_lang('Position'),
9166
            [],
9167
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9168
        );
9169
        $selectPrevious->addOption(get_lang('First position'), 0);
9170
9171
        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...
9172
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9173
                $selectPrevious->addOption(
9174
                    $arrLP[$i]['title'],
9175
                    $arrLP[$i]['id']
9176
                );
9177
9178
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9179
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9180
                } elseif ($action == 'add') {
9181
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9182
                }
9183
            }
9184
        }
9185
9186
        if ($action != 'move') {
9187
            $urlAttributes = ['class' => 'learnpath_item_form'];
9188
9189
            if (is_numeric($extra_info)) {
9190
                $urlAttributes['disabled'] = 'disabled';
9191
            }
9192
9193
            $form->addElement('url', 'url', get_lang('URL'), $urlAttributes);
9194
            $defaults['url'] = $item_url;
9195
            $arrHide = [];
9196
            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...
9197
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
9198
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9199
                }
9200
            }
9201
        }
9202
9203
        if ($action == 'add') {
9204
            $form->addButtonSave(get_lang('Add link to course'), 'submit_button');
9205
        } else {
9206
            $form->addButtonSave(get_lang('Edit the current link'), 'submit_button');
9207
        }
9208
9209
        if ($action == 'move') {
9210
            $form->addHidden('title', $item_title);
9211
            $form->addHidden('description', $item_description);
9212
        }
9213
9214
        if (is_numeric($extra_info)) {
9215
            $form->addHidden('path', $extra_info);
9216
        } elseif (is_array($extra_info)) {
9217
            $form->addHidden('path', $extra_info['path']);
9218
        }
9219
        $form->addHidden('type', TOOL_LINK);
9220
        $form->addHidden('post_time', time());
9221
        $form->setDefaults($defaults);
9222
9223
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
9224
    }
9225
9226
    /**
9227
     * Return HTML form to add/edit a student publication (work).
9228
     *
9229
     * @param string $action
9230
     * @param int    $id         Item ID if already exists
9231
     * @param string $extra_info
9232
     *
9233
     * @throws Exception
9234
     *
9235
     * @return string HTML form
9236
     */
9237
    public function display_student_publication_form(
9238
        $action = 'add',
9239
        $id = 0,
9240
        $extra_info = ''
9241
    ) {
9242
        $course_id = api_get_course_int_id();
9243
        $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
9244
9245
        $item_title = get_lang('Assignments');
9246
        if ($id != 0 && is_array($extra_info)) {
9247
            $item_title = stripslashes($extra_info['title']);
9248
            $item_description = stripslashes($extra_info['description']);
9249
        } elseif (is_numeric($extra_info)) {
9250
            $extra_info = (int) $extra_info;
9251
            $sql = "SELECT title, description
9252
                    FROM $tbl_publication
9253
                    WHERE c_id = $course_id AND id = ".$extra_info;
9254
9255
            $result = Database::query($sql);
9256
            $row = Database::fetch_array($result);
9257
            if ($row) {
9258
                $item_title = $row['title'];
9259
            }
9260
        }
9261
9262
        $parent = 0;
9263
        if ($id != 0 && is_array($extra_info)) {
9264
            $parent = $extra_info['parent_item_id'];
9265
        }
9266
9267
        $arrLP = $this->getItemsForForm();
9268
9269
        $this->tree_array($arrLP);
9270
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9271
        unset($this->arrMenu);
9272
9273
        $form = new FormValidator('frm_student_publication', 'post', '#');
9274
9275
        if ($action == 'add') {
9276
            $form->addHeader(get_lang('Assignments'));
9277
        } elseif ($action == 'move') {
9278
            $form->addHeader(get_lang('Move the current assignment'));
9279
        } else {
9280
            $form->addHeader(get_lang('Edit the current assignment'));
9281
        }
9282
9283
        if ($action != 'move') {
9284
            $this->setItemTitle($form);
9285
        }
9286
9287
        $parentSelect = $form->addSelect(
9288
            'parent',
9289
            get_lang('Parent'),
9290
            ['0' => $this->name],
9291
            [
9292
                'onchange' => 'javascript: load_cbo(this.value);',
9293
                'class' => 'learnpath_item_form',
9294
                'id' => 'idParent',
9295
            ]
9296
        );
9297
9298
        $arrHide = [$id];
9299
        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...
9300
            if ($action != 'add') {
9301
                if (
9302
                    ($arrLP[$i]['item_type'] == 'dir') &&
9303
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9304
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9305
                ) {
9306
                    $parentSelect->addOption(
9307
                        $arrLP[$i]['title'],
9308
                        $arrLP[$i]['id'],
9309
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9310
                    );
9311
9312
                    if ($parent == $arrLP[$i]['id']) {
9313
                        $parentSelect->setSelected($arrLP[$i]['id']);
9314
                    }
9315
                } else {
9316
                    $arrHide[] = $arrLP[$i]['id'];
9317
                }
9318
            } else {
9319
                if ($arrLP[$i]['item_type'] == 'dir') {
9320
                    $parentSelect->addOption(
9321
                        $arrLP[$i]['title'],
9322
                        $arrLP[$i]['id'],
9323
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9324
                    );
9325
9326
                    if ($parent == $arrLP[$i]['id']) {
9327
                        $parentSelect->setSelected($arrLP[$i]['id']);
9328
                    }
9329
                }
9330
            }
9331
        }
9332
9333
        if (is_array($arrLP)) {
9334
            reset($arrLP);
9335
        }
9336
9337
        $previousSelect = $form->addSelect(
9338
            'previous',
9339
            get_lang('Position'),
9340
            ['0' => get_lang('First position')],
9341
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9342
        );
9343
9344
        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...
9345
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9346
                $previousSelect->addOption(
9347
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
9348
                    $arrLP[$i]['id']
9349
                );
9350
9351
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9352
                    $previousSelect->setSelected($arrLP[$i]['id']);
9353
                } elseif ($action == 'add') {
9354
                    $previousSelect->setSelected($arrLP[$i]['id']);
9355
                }
9356
            }
9357
        }
9358
9359
        if ($action == 'add') {
9360
            $form->addButtonCreate(get_lang('Add assignment to course'), 'submit_button');
9361
        } else {
9362
            $form->addButtonCreate(get_lang('Edit the current assignment'), 'submit_button');
9363
        }
9364
9365
        if ($action == 'move') {
9366
            $form->addHidden('title', $item_title);
9367
            $form->addHidden('description', $item_description);
9368
        }
9369
9370
        if (is_numeric($extra_info)) {
9371
            $form->addHidden('path', $extra_info);
9372
        } elseif (is_array($extra_info)) {
9373
            $form->addHidden('path', $extra_info['path']);
9374
        }
9375
9376
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
9377
        $form->addHidden('post_time', time());
9378
        $form->setDefaults(['title' => $item_title]);
9379
9380
        $return = '<div class="sectioncomment">';
9381
        $return .= $form->returnForm();
9382
        $return .= '</div>';
9383
9384
        return $return;
9385
    }
9386
9387
    /**
9388
     * Displays the menu for manipulating a step.
9389
     *
9390
     * @param id     $item_id
9391
     * @param string $item_type
9392
     *
9393
     * @return string
9394
     */
9395
    public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
9396
    {
9397
        $_course = api_get_course_info();
9398
        $course_code = api_get_course_id();
9399
        $item_id = (int) $item_id;
9400
9401
        $return = '<div class="actions">';
9402
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9403
        $sql = "SELECT * FROM $tbl_lp_item
9404
                WHERE iid = ".$item_id;
9405
        $result = Database::query($sql);
9406
        $row = Database::fetch_assoc($result);
9407
9408
        $audio_player = null;
9409
        // We display an audio player if needed.
9410
        if (!empty($row['audio'])) {
9411
            $webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
9412
9413
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
9414
                .'<audio src="'.$webAudioPath.'" controls>'
9415
                .'</div><br>';
9416
        }
9417
9418
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
9419
9420
        if ($item_type != TOOL_LP_FINAL_ITEM) {
9421
            $return .= Display::url(
9422
                Display::return_icon(
9423
                    'edit.png',
9424
                    get_lang('Edit'),
9425
                    [],
9426
                    ICON_SIZE_SMALL
9427
                ),
9428
                $url.'&action=edit_item&path_item='.$row['path']
9429
            );
9430
9431
            $return .= Display::url(
9432
                Display::return_icon(
9433
                    'move.png',
9434
                    get_lang('Move'),
9435
                    [],
9436
                    ICON_SIZE_SMALL
9437
                ),
9438
                $url.'&action=move_item'
9439
            );
9440
        }
9441
9442
        // Commented for now as prerequisites cannot be added to chapters.
9443
        if ($item_type != 'dir') {
9444
            $return .= Display::url(
9445
                Display::return_icon(
9446
                    'accept.png',
9447
                    get_lang('Prerequisites'),
9448
                    [],
9449
                    ICON_SIZE_SMALL
9450
                ),
9451
                $url.'&action=edit_item_prereq'
9452
            );
9453
        }
9454
        $return .= Display::url(
9455
            Display::return_icon(
9456
                'delete.png',
9457
                get_lang('Delete'),
9458
                [],
9459
                ICON_SIZE_SMALL
9460
            ),
9461
            $url.'&action=delete_item'
9462
        );
9463
9464
        if (in_array($item_type, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
9465
            $documentData = DocumentManager::get_document_data_by_id($row['path'], $course_code);
9466
            if (empty($documentData)) {
9467
                // Try with iid
9468
                $table = Database::get_course_table(TABLE_DOCUMENT);
9469
                $sql = "SELECT path FROM $table
9470
                        WHERE
9471
                              c_id = ".api_get_course_int_id()." AND
9472
                              iid = ".$row['path']." AND
9473
                              path NOT LIKE '%_DELETED_%'";
9474
                $result = Database::query($sql);
9475
                $documentData = Database::fetch_array($result);
9476
                if ($documentData) {
9477
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
9478
                }
9479
            }
9480
            if (isset($documentData['absolute_path_from_document'])) {
9481
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
9482
            }
9483
        }
9484
9485
        $return .= '</div>';
9486
9487
        if (!empty($audio_player)) {
9488
            $return .= $audio_player;
9489
        }
9490
9491
        return $return;
9492
    }
9493
9494
    /**
9495
     * Creates the javascript needed for filling up the checkboxes without page reload.
9496
     *
9497
     * @return string
9498
     */
9499
    public function get_js_dropdown_array()
9500
    {
9501
        $course_id = api_get_course_int_id();
9502
        $return = 'var child_name = new Array();'."\n";
9503
        $return .= 'var child_value = new Array();'."\n\n";
9504
        $return .= 'child_name[0] = new Array();'."\n";
9505
        $return .= 'child_value[0] = new Array();'."\n\n";
9506
9507
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9508
        $sql = "SELECT * FROM ".$tbl_lp_item."
9509
                WHERE
9510
                    c_id = $course_id AND
9511
                    lp_id = ".$this->lp_id." AND
9512
                    parent_item_id = 0
9513
                ORDER BY display_order ASC";
9514
        $res_zero = Database::query($sql);
9515
        $i = 0;
9516
9517
        $list = $this->getItemsForForm(true);
9518
9519
        foreach ($list as $row_zero) {
9520
            if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
9521
                if ($row_zero['item_type'] == TOOL_QUIZ) {
9522
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
9523
                }
9524
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
9525
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
9526
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
9527
            }
9528
        }
9529
9530
        $return .= "\n";
9531
        $sql = "SELECT * FROM $tbl_lp_item
9532
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9533
        $res = Database::query($sql);
9534
        while ($row = Database::fetch_array($res)) {
9535
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
9536
                           WHERE
9537
                                c_id = ".$course_id." AND
9538
                                parent_item_id = ".$row['iid']."
9539
                           ORDER BY display_order ASC";
9540
            $res_parent = Database::query($sql_parent);
9541
            $i = 0;
9542
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
9543
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
9544
9545
            while ($row_parent = Database::fetch_array($res_parent)) {
9546
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
9547
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
9548
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
9549
            }
9550
            $return .= "\n";
9551
        }
9552
9553
        $return .= "
9554
            function load_cbo(id) {
9555
                if (!id) {
9556
                    return false;
9557
                }
9558
9559
                var cbo = document.getElementById('previous');
9560
                for(var i = cbo.length - 1; i > 0; i--) {
9561
                    cbo.options[i] = null;
9562
                }
9563
9564
                var k=0;
9565
                for(var i = 1; i <= child_name[id].length; i++){
9566
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
9567
                    option.style.paddingLeft = '40px';
9568
                    cbo.options[i] = option;
9569
                    k = i;
9570
                }
9571
9572
                cbo.options[k].selected = true;
9573
                $('#previous').selectpicker('refresh');
9574
            }";
9575
9576
        return $return;
9577
    }
9578
9579
    /**
9580
     * Display the form to allow moving an item.
9581
     *
9582
     * @param int $item_id Item ID
9583
     *
9584
     * @throws Exception
9585
     * @throws HTML_QuickForm_Error
9586
     *
9587
     * @return string HTML form
9588
     */
9589
    public function display_move_item($item_id)
9590
    {
9591
        $return = '';
9592
        if (is_numeric($item_id)) {
9593
            $item_id = (int) $item_id;
9594
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9595
9596
            $sql = "SELECT * FROM $tbl_lp_item
9597
                    WHERE iid = $item_id";
9598
            $res = Database::query($sql);
9599
            $row = Database::fetch_array($res);
9600
9601
            switch ($row['item_type']) {
9602
                case 'dir':
9603
                case 'asset':
9604
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9605
                    $return .= $this->display_item_form(
9606
                        $row['item_type'],
9607
                        get_lang('Move the current section'),
9608
                        'move',
9609
                        $item_id,
9610
                        $row
9611
                    );
9612
                    break;
9613
                case TOOL_DOCUMENT:
9614
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9615
                    $return .= $this->display_document_form('move', $item_id, $row);
9616
                    break;
9617
                case TOOL_LINK:
9618
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9619
                    $return .= $this->display_link_form('move', $item_id, $row);
9620
                    break;
9621
                case TOOL_HOTPOTATOES:
9622
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9623
                    $return .= $this->display_link_form('move', $item_id, $row);
9624
                    break;
9625
                case TOOL_QUIZ:
9626
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9627
                    $return .= $this->display_quiz_form('move', $item_id, $row);
9628
                    break;
9629
                case TOOL_STUDENTPUBLICATION:
9630
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9631
                    $return .= $this->display_student_publication_form('move', $item_id, $row);
9632
                    break;
9633
                case TOOL_FORUM:
9634
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9635
                    $return .= $this->display_forum_form('move', $item_id, $row);
9636
                    break;
9637
                case TOOL_THREAD:
9638
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9639
                    $return .= $this->display_forum_form('move', $item_id, $row);
9640
                    break;
9641
            }
9642
        }
9643
9644
        return $return;
9645
    }
9646
9647
    /**
9648
     * Return HTML form to allow prerequisites selection.
9649
     *
9650
     * @todo use FormValidator
9651
     *
9652
     * @param int Item ID
9653
     *
9654
     * @return string HTML form
9655
     */
9656
    public function display_item_prerequisites_form($item_id = 0)
9657
    {
9658
        $course_id = api_get_course_int_id();
9659
        $item_id = (int) $item_id;
9660
9661
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9662
9663
        /* Current prerequisite */
9664
        $sql = "SELECT * FROM $tbl_lp_item
9665
                WHERE iid = $item_id";
9666
        $result = Database::query($sql);
9667
        $row = Database::fetch_array($result);
9668
        $prerequisiteId = $row['prerequisite'];
9669
        $return = '<legend>';
9670
        $return .= get_lang('Add/edit prerequisites');
9671
        $return .= '</legend>';
9672
        $return .= '<form method="POST">';
9673
        $return .= '<div class="table-responsive">';
9674
        $return .= '<table class="table table-hover">';
9675
        $return .= '<thead>';
9676
        $return .= '<tr>';
9677
        $return .= '<th>'.get_lang('Prerequisites').'</th>';
9678
        $return .= '<th width="140">'.get_lang('minimum').'</th>';
9679
        $return .= '<th width="140">'.get_lang('maximum').'</th>';
9680
        $return .= '</tr>';
9681
        $return .= '</thead>';
9682
9683
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
9684
        $return .= '<tbody>';
9685
        $return .= '<tr>';
9686
        $return .= '<td colspan="3">';
9687
        $return .= '<div class="radio learnpath"><label for="idnone">';
9688
        $return .= '<input checked="checked" id="idnone" name="prerequisites" type="radio" />';
9689
        $return .= get_lang('none').'</label>';
9690
        $return .= '</div>';
9691
        $return .= '</tr>';
9692
9693
        $sql = "SELECT * FROM $tbl_lp_item
9694
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9695
        $result = Database::query($sql);
9696
9697
        $selectedMinScore = [];
9698
        $selectedMaxScore = [];
9699
        $masteryScore = [];
9700
        while ($row = Database::fetch_array($result)) {
9701
            if ($row['iid'] == $item_id) {
9702
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
9703
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
9704
            }
9705
            $masteryScore[$row['iid']] = $row['mastery_score'];
9706
        }
9707
9708
        $arrLP = $this->getItemsForForm();
9709
        $this->tree_array($arrLP);
9710
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9711
        unset($this->arrMenu);
9712
9713
        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...
9714
            $item = $arrLP[$i];
9715
9716
            if ($item['id'] == $item_id) {
9717
                break;
9718
            }
9719
9720
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
9721
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
9722
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
9723
9724
            $return .= '<tr>';
9725
            $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
9726
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
9727
            $return .= '<label for="id'.$item['id'].'">';
9728
            $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'].'" />';
9729
9730
            $icon_name = str_replace(' ', '', $item['item_type']);
9731
9732
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
9733
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
9734
            } else {
9735
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
9736
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
9737
                } else {
9738
                    $return .= Display::return_icon('folder_document.png');
9739
                }
9740
            }
9741
9742
            $return .= $item['title'].'</label>';
9743
            $return .= '</div>';
9744
            $return .= '</td>';
9745
9746
            if ($item['item_type'] == TOOL_QUIZ) {
9747
                // lets update max_score Tests information depending of the Tests Advanced properties
9748
                $lpItemObj = new LpItem($course_id, $item['id']);
9749
                $exercise = new Exercise($course_id);
9750
                $exercise->read($lpItemObj->path);
9751
                $lpItemObj->max_score = $exercise->get_max_score();
9752
                $lpItemObj->update();
9753
                $item['max_score'] = $lpItemObj->max_score;
9754
9755
                if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
9756
                    // Backwards compatibility with 1.9.x use mastery_score as min value
9757
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
9758
                }
9759
9760
                $return .= '<td>';
9761
                $return .= '<input
9762
                    class="form-control"
9763
                    size="4" maxlength="3"
9764
                    name="min_'.$item['id'].'"
9765
                    type="number"
9766
                    min="0"
9767
                    step="1"
9768
                    max="'.$item['max_score'].'"
9769
                    value="'.$selectedMinScoreValue.'"
9770
                />';
9771
                $return .= '</td>';
9772
                $return .= '<td>';
9773
                $return .= '<input
9774
                    class="form-control"
9775
                    size="4"
9776
                    maxlength="3"
9777
                    name="max_'.$item['id'].'"
9778
                    type="number"
9779
                    min="0"
9780
                    step="1"
9781
                    max="'.$item['max_score'].'"
9782
                    value="'.$selectedMaxScoreValue.'"
9783
                />';
9784
                $return .= '</td>';
9785
            }
9786
9787
            if ($item['item_type'] == TOOL_HOTPOTATOES) {
9788
                $return .= '<td>';
9789
                $return .= '<input
9790
                    size="4"
9791
                    maxlength="3"
9792
                    name="min_'.$item['id'].'"
9793
                    type="number"
9794
                    min="0"
9795
                    step="1"
9796
                    max="'.$item['max_score'].'"
9797
                    value="'.$selectedMinScoreValue.'"
9798
                />';
9799
                $return .= '</td>';
9800
                $return .= '<td>';
9801
                $return .= '<input
9802
                    size="4"
9803
                    maxlength="3"
9804
                    name="max_'.$item['id'].'"
9805
                    type="number"
9806
                    min="0"
9807
                    step="1"
9808
                    max="'.$item['max_score'].'"
9809
                    value="'.$selectedMaxScoreValue.'"
9810
                />';
9811
                $return .= '</td>';
9812
            }
9813
            $return .= '</tr>';
9814
        }
9815
        $return .= '<tr>';
9816
        $return .= '</tr>';
9817
        $return .= '</tbody>';
9818
        $return .= '</table>';
9819
        $return .= '</div>';
9820
        $return .= '<div class="form-group">';
9821
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
9822
            get_lang('Save prerequisites settings').'</button>';
9823
        $return .= '</form>';
9824
9825
        return $return;
9826
    }
9827
9828
    /**
9829
     * Return HTML list to allow prerequisites selection for lp.
9830
     *
9831
     * @return string HTML form
9832
     */
9833
    public function display_lp_prerequisites_list()
9834
    {
9835
        $course_id = api_get_course_int_id();
9836
        $lp_id = $this->lp_id;
9837
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
9838
9839
        // get current prerequisite
9840
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
9841
        $result = Database::query($sql);
9842
        $row = Database::fetch_array($result);
9843
        $prerequisiteId = $row['prerequisite'];
9844
        $session_id = api_get_session_id();
9845
        $session_condition = api_get_session_condition($session_id, true, true);
9846
        $sql = "SELECT * FROM $tbl_lp
9847
                WHERE c_id = $course_id $session_condition
9848
                ORDER BY display_order ";
9849
        $rs = Database::query($sql);
9850
        $return = '';
9851
        $return .= '<select name="prerequisites" class="form-control">';
9852
        $return .= '<option value="0">'.get_lang('none').'</option>';
9853
        if (Database::num_rows($rs) > 0) {
9854
            while ($row = Database::fetch_array($rs)) {
9855
                if ($row['id'] == $lp_id) {
9856
                    continue;
9857
                }
9858
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
9859
            }
9860
        }
9861
        $return .= '</select>';
9862
9863
        return $return;
9864
    }
9865
9866
    /**
9867
     * Creates a list with all the documents in it.
9868
     *
9869
     * @param bool $showInvisibleFiles
9870
     *
9871
     * @throws Exception
9872
     * @throws HTML_QuickForm_Error
9873
     *
9874
     * @return string
9875
     */
9876
    public function get_documents($showInvisibleFiles = false)
9877
    {
9878
        $course_info = api_get_course_info();
9879
        $sessionId = api_get_session_id();
9880
        $documentTree = DocumentManager::get_document_preview(
9881
            $course_info,
9882
            $this->lp_id,
9883
            null,
9884
            $sessionId,
9885
            true,
9886
            null,
9887
            null,
9888
            $showInvisibleFiles,
9889
            true
9890
        );
9891
9892
        $headers = [
9893
            get_lang('Files'),
9894
            get_lang('Create a new document'),
9895
            get_lang('Create read-out text'),
9896
            get_lang('Upload'),
9897
        ];
9898
9899
        $form = new FormValidator(
9900
            'form_upload',
9901
            'POST',
9902
            $this->getCurrentBuildingModeURL(),
9903
            '',
9904
            ['enctype' => 'multipart/form-data']
9905
        );
9906
9907
        $folders = DocumentManager::get_all_document_folders(
9908
            api_get_course_info(),
9909
            0,
9910
            true
9911
        );
9912
9913
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
9914
9915
        DocumentManager::build_directory_selector(
9916
            $folders,
9917
            $lpPathInfo['id'],
9918
            [],
9919
            true,
9920
            $form,
9921
            'directory_parent_id'
9922
        );
9923
9924
        $group = [
9925
            $form->createElement(
9926
                'radio',
9927
                'if_exists',
9928
                get_lang('If file exists:'),
9929
                get_lang('Do nothing'),
9930
                'nothing'
9931
            ),
9932
            $form->createElement(
9933
                'radio',
9934
                'if_exists',
9935
                null,
9936
                get_lang('Overwrite the existing file'),
9937
                'overwrite'
9938
            ),
9939
            $form->createElement(
9940
                'radio',
9941
                'if_exists',
9942
                null,
9943
                get_lang('Rename the uploaded file if it exists'),
9944
                'rename'
9945
            ),
9946
        ];
9947
        $form->addGroup($group, null, get_lang('If file exists:'));
9948
9949
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
9950
        $defaultFileExistsOption = 'rename';
9951
        if (!empty($fileExistsOption)) {
9952
            $defaultFileExistsOption = $fileExistsOption;
9953
        }
9954
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
9955
9956
        // Check box options
9957
        $form->addElement(
9958
            'checkbox',
9959
            'unzip',
9960
            get_lang('Options'),
9961
            get_lang('Uncompress zip')
9962
        );
9963
9964
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
9965
        $form->addMultipleUpload($url);
9966
        $new = $this->display_document_form('add', 0);
9967
        $frmReadOutText = $this->displayFrmReadOutText('add');
9968
        $tabs = Display::tabs(
9969
            $headers,
9970
            [$documentTree, $new, $frmReadOutText, $form->returnForm()],
9971
            'subtab'
9972
        );
9973
9974
        return $tabs;
9975
    }
9976
9977
    /**
9978
     * Creates a list with all the exercises (quiz) in it.
9979
     *
9980
     * @return string
9981
     */
9982
    public function get_exercises()
9983
    {
9984
        $course_id = api_get_course_int_id();
9985
        $session_id = api_get_session_id();
9986
        $userInfo = api_get_user_info();
9987
9988
        // New for hotpotatoes.
9989
        //$uploadPath = DIR_HOTPOTATOES; //defined in main_api
9990
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
9991
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
9992
        $condition_session = api_get_session_condition($session_id, true, true);
9993
        $setting = api_get_setting('lp.show_invisible_exercise_in_lp_toc') === 'true';
9994
9995
        $activeCondition = ' active <> -1 ';
9996
        if ($setting) {
9997
            $activeCondition = ' active = 1 ';
9998
        }
9999
10000
        $categoryCondition = '';
10001
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
10002
        if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
10003
            $categoryCondition = " AND exercise_category_id = $categoryId ";
10004
        }
10005
10006
        $keywordCondition = '';
10007
        $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
10008
10009
        if (!empty($keyword)) {
10010
            $keyword = Database::escape_string($keyword);
10011
            $keywordCondition = " AND title LIKE '%$keyword%' ";
10012
        }
10013
10014
        $sql_quiz = "SELECT * FROM $tbl_quiz
10015
                     WHERE
10016
                            c_id = $course_id AND
10017
                            $activeCondition
10018
                            $condition_session
10019
                            $categoryCondition
10020
                            $keywordCondition
10021
                     ORDER BY title ASC";
10022
        $res_quiz = Database::query($sql_quiz);
10023
10024
        /*$sql_hot = "SELECT * FROM $tbl_doc
10025
                    WHERE
10026
                        c_id = $course_id AND
10027
                        path LIKE '".$uploadPath."/%/%htm%'
10028
                        $condition_session
10029
                     ORDER BY id ASC";
10030
10031
        $res_quiz = Database::query($sql_quiz);
10032
        $res_hot = Database::query($sql_hot);*/
10033
10034
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
10035
10036
        // Create a search-box
10037
        $form = new FormValidator('search_simple', 'get', $currentUrl);
10038
        $form->addHidden('action', 'add_item');
10039
        $form->addHidden('type', 'step');
10040
        $form->addHidden('lp_id', $this->lp_id);
10041
        $form->addHidden('lp_build_selected', '2');
10042
10043
        $form->addCourseHiddenParams();
10044
        $form->addText(
10045
            'keyword',
10046
            get_lang('Search'),
10047
            false,
10048
            [
10049
                'aria-label' => get_lang('Search'),
10050
            ]
10051
        );
10052
10053
        if (api_get_configuration_value('allow_exercise_categories')) {
10054
            $manager = new ExerciseCategoryManager();
10055
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
10056
            if (!empty($options)) {
10057
                $form->addSelect(
10058
                    'category_id',
10059
                    get_lang('Category'),
10060
                    $options,
10061
                    ['placeholder' => get_lang('Please select an option')]
10062
                );
10063
            }
10064
        }
10065
10066
        $form->addButtonSearch(get_lang('Search'));
10067
        $return = $form->returnForm();
10068
10069
        $return .= '<ul class="lp_resource">';
10070
10071
        $return .= '<li class="lp_resource_element">';
10072
        $return .= Display::return_icon('new_exercice.png');
10073
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
10074
            get_lang('New test').'</a>';
10075
        $return .= '</li>';
10076
10077
        $previewIcon = Display::return_icon(
10078
            'preview_view.png',
10079
            get_lang('Preview')
10080
        );
10081
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
10082
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10083
        //$exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/showinframes.php?'.api_get_cidreq();
10084
        // Display hotpotatoes
10085
        /*while ($row_hot = Database::fetch_array($res_hot)) {
10086
            $link = Display::url(
10087
                $previewIcon,
10088
                $exerciseUrl.'&file='.$row_hot['path'],
10089
                ['target' => '_blank']
10090
            );
10091
            $return .= '<li class="lp_resource_element" data_id="'.$row_hot['id'].'" data_type="hotpotatoes" title="'.$row_hot['title'].'" >';
10092
            $return .= '<a class="moved" href="#">';
10093
            $return .= Display::return_icon(
10094
                'move_everywhere.png',
10095
                get_lang('Move'),
10096
                [],
10097
                ICON_SIZE_TINY
10098
            );
10099
            $return .= '</a> ';
10100
            $return .= Display::return_icon('hotpotatoes_s.png');
10101
            $return .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_HOTPOTATOES.'&file='.$row_hot['id'].'&lp_id='.$this->lp_id.'">'.
10102
                ((!empty($row_hot['comment'])) ? $row_hot['comment'] : Security::remove_XSS($row_hot['title'])).$link.'</a>';
10103
            $return .= '</li>';
10104
        }*/
10105
10106
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
10107
        while ($row_quiz = Database::fetch_array($res_quiz)) {
10108
            $title = strip_tags(
10109
                api_html_entity_decode($row_quiz['title'])
10110
            );
10111
10112
            $visibility = api_get_item_visibility(
10113
                ['real_id' => $course_id],
10114
                TOOL_QUIZ,
10115
                $row_quiz['iid'],
10116
                $session_id
10117
            );
10118
10119
            $link = Display::url(
10120
                $previewIcon,
10121
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
10122
                ['target' => '_blank']
10123
            );
10124
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
10125
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
10126
            $return .= $quizIcon;
10127
            $sessionStar = api_get_session_image(
10128
                $row_quiz['session_id'],
10129
                $userInfo['status']
10130
            );
10131
            $return .= Display::url(
10132
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
10133
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['id'].'&lp_id='.$this->lp_id,
10134
                [
10135
                    'class' => $visibility == 0 ? 'moved text-muted' : 'moved',
10136
                ]
10137
            );
10138
            $return .= '</li>';
10139
        }
10140
10141
        $return .= '</ul>';
10142
10143
        return $return;
10144
    }
10145
10146
    /**
10147
     * Creates a list with all the links in it.
10148
     *
10149
     * @return string
10150
     */
10151
    public function get_links()
10152
    {
10153
        $selfUrl = api_get_self();
10154
        $courseIdReq = api_get_cidreq();
10155
        $course = api_get_course_info();
10156
        $userInfo = api_get_user_info();
10157
10158
        $course_id = $course['real_id'];
10159
        $tbl_link = Database::get_course_table(TABLE_LINK);
10160
        $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
10161
        $moveEverywhereIcon = Display::return_icon(
10162
            'move_everywhere.png',
10163
            get_lang('Move'),
10164
            [],
10165
            ICON_SIZE_TINY
10166
        );
10167
10168
        $session_id = api_get_session_id();
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
        while ($link = Database::fetch_array($result)) {
10192
            if (!$link['category_id']) {
10193
                $link['category_title'] = get_lang('Uncategorized');
10194
            }
10195
            $categories[$link['category_id']] = $link['category_title'];
10196
            $categorizedLinks[$link['category_id']][$link['link_id']] = $link;
10197
        }
10198
10199
        $linksHtmlCode =
10200
            '<script>
10201
            function toggle_tool(tool, id) {
10202
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
10203
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
10204
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10205
                } else {
10206
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
10207
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
10208
                }
10209
            }
10210
        </script>
10211
10212
        <ul class="lp_resource">
10213
            <li class="lp_resource_element">
10214
                '.Display::return_icon('linksnew.gif').'
10215
                <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').'">'.
10216
                get_lang('Add a link').'
10217
                </a>
10218
            </li>';
10219
10220
        foreach ($categorizedLinks as $categoryId => $links) {
10221
            $linkNodes = null;
10222
            foreach ($links as $key => $linkInfo) {
10223
                $title = $linkInfo['link_title'];
10224
                $linkSessionId = $linkInfo['link_session_id'];
10225
10226
                $link = Display::url(
10227
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10228
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
10229
                    ['target' => '_blank']
10230
                );
10231
10232
                if (api_get_item_visibility($course, TOOL_LINK, $key, $session_id) != 2) {
10233
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
10234
                    $linkNodes .=
10235
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
10236
                        <a class="moved" href="#">'.
10237
                            $moveEverywhereIcon.
10238
                        '</a>
10239
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
10240
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
10241
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
10242
                        Security::remove_XSS($title).$sessionStar.$link.
10243
                        '</a>
10244
                    </li>';
10245
                }
10246
            }
10247
            $linksHtmlCode .=
10248
                '<li>
10249
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
10250
                    <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
10251
                    align="absbottom" />
10252
                </a>
10253
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
10254
            </li>
10255
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
10256
        }
10257
        $linksHtmlCode .= '</ul>';
10258
10259
        return $linksHtmlCode;
10260
    }
10261
10262
    /**
10263
     * Creates a list with all the student publications in it.
10264
     *
10265
     * @return string
10266
     */
10267
    public function get_student_publications()
10268
    {
10269
        $return = '<ul class="lp_resource">';
10270
        $return .= '<li class="lp_resource_element">';
10271
        $return .= Display::return_icon('works_new.gif');
10272
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
10273
            get_lang('Add the Assignments tool to the course').'</a>';
10274
        $return .= '</li>';
10275
10276
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
10277
        $works = getWorkListTeacher(0, 100, null, null, null);
10278
        if (!empty($works)) {
10279
            foreach ($works as $work) {
10280
                $link = Display::url(
10281
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10282
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
10283
                    ['target' => '_blank']
10284
                );
10285
10286
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
10287
                $return .= '<a class="moved" href="#">';
10288
                $return .= Display::return_icon(
10289
                    'move_everywhere.png',
10290
                    get_lang('Move'),
10291
                    [],
10292
                    ICON_SIZE_TINY
10293
                );
10294
                $return .= '</a> ';
10295
10296
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
10297
                $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.'">'.
10298
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
10299
                </a>';
10300
10301
                $return .= '</li>';
10302
            }
10303
        }
10304
10305
        $return .= '</ul>';
10306
10307
        return $return;
10308
    }
10309
10310
    /**
10311
     * Creates a list with all the forums in it.
10312
     *
10313
     * @return string
10314
     */
10315
    public function get_forums()
10316
    {
10317
        require_once '../forum/forumfunction.inc.php';
10318
10319
        $forumCategories = get_forum_categories();
10320
        $forumsInNoCategory = get_forums_in_category(0);
10321
        if (!empty($forumsInNoCategory)) {
10322
            $forumCategories = array_merge(
10323
                $forumCategories,
10324
                [
10325
                    [
10326
                        'cat_id' => 0,
10327
                        'session_id' => 0,
10328
                        'visibility' => 1,
10329
                        'cat_comment' => null,
10330
                    ],
10331
                ]
10332
            );
10333
        }
10334
10335
        $forumList = get_forums();
10336
        $a_forums = [];
10337
        foreach ($forumCategories as $forumCategory) {
10338
            // The forums in this category.
10339
            $forumsInCategory = get_forums_in_category($forumCategory['cat_id']);
10340
            if (!empty($forumsInCategory)) {
10341
                foreach ($forumList as $forum) {
10342
                    if (isset($forum['forum_category']) &&
10343
                        $forum['forum_category'] == $forumCategory['cat_id']
10344
                    ) {
10345
                        $a_forums[] = $forum;
10346
                    }
10347
                }
10348
            }
10349
        }
10350
10351
        $return = '<ul class="lp_resource">';
10352
10353
        // First add link
10354
        $return .= '<li class="lp_resource_element">';
10355
        $return .= Display::return_icon('new_forum.png');
10356
        $return .= Display::url(
10357
            get_lang('Create a new forum'),
10358
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
10359
                'action' => 'add',
10360
                'content' => 'forum',
10361
                'lp_id' => $this->lp_id,
10362
            ]),
10363
            ['title' => get_lang('Create a new forum')]
10364
        );
10365
        $return .= '</li>';
10366
10367
        $return .= '<script>
10368
            function toggle_forum(forum_id) {
10369
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
10370
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
10371
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10372
                } else {
10373
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
10374
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
10375
                }
10376
            }
10377
        </script>';
10378
10379
        foreach ($a_forums as $forum) {
10380
            if (!empty($forum['forum_id'])) {
10381
                $link = Display::url(
10382
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10383
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forum['forum_id'],
10384
                    ['target' => '_blank']
10385
                );
10386
10387
                $return .= '<li class="lp_resource_element" data_id="'.$forum['forum_id'].'" data_type="'.TOOL_FORUM.'" title="'.$forum['forum_title'].'" >';
10388
                $return .= '<a class="moved" href="#">';
10389
                $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10390
                $return .= ' </a>';
10391
                $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
10392
                $return .= '<a onclick="javascript:toggle_forum('.$forum['forum_id'].');" style="cursor:hand; vertical-align:middle">
10393
                                <img src="'.Display::returnIconPath('add.png').'" id="forum_'.$forum['forum_id'].'_opener" align="absbottom" />
10394
                            </a>
10395
                            <a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_FORUM.'&forum_id='.$forum['forum_id'].'&lp_id='.$this->lp_id.'" style="vertical-align:middle">'.
10396
                    Security::remove_XSS($forum['forum_title']).' '.$link.'</a>';
10397
10398
                $return .= '</li>';
10399
10400
                $return .= '<div style="display:none" id="forum_'.$forum['forum_id'].'_content">';
10401
                $a_threads = get_threads($forum['forum_id']);
10402
                if (is_array($a_threads)) {
10403
                    foreach ($a_threads as $thread) {
10404
                        $link = Display::url(
10405
                            Display::return_icon('preview_view.png', get_lang('Preview')),
10406
                            api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forum['forum_id'].'&thread='.$thread['thread_id'],
10407
                            ['target' => '_blank']
10408
                        );
10409
10410
                        $return .= '<li class="lp_resource_element" data_id="'.$thread['thread_id'].'" data_type="'.TOOL_THREAD.'" title="'.$thread['thread_title'].'" >';
10411
                        $return .= '&nbsp;<a class="moved" href="#">';
10412
                        $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10413
                        $return .= ' </a>';
10414
                        $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
10415
                        $return .= '<a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_THREAD.'&thread_id='.$thread['thread_id'].'&lp_id='.$this->lp_id.'">'.
10416
                            Security::remove_XSS($thread['thread_title']).' '.$link.'</a>';
10417
                        $return .= '</li>';
10418
                    }
10419
                }
10420
                $return .= '</div>';
10421
            }
10422
        }
10423
        $return .= '</ul>';
10424
10425
        return $return;
10426
    }
10427
10428
    /**
10429
     * // TODO: The output encoding should be equal to the system encoding.
10430
     *
10431
     * Exports the learning path as a SCORM package. This is the main function that
10432
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
10433
     * whole thing and returns the zip.
10434
     *
10435
     * This method needs to be called in PHP5, as it will fail with non-adequate
10436
     * XML package (like the ones for PHP4), and it is *not* a static method, so
10437
     * you need to call it on a learnpath object.
10438
     *
10439
     * @TODO The method might be redefined later on in the scorm class itself to avoid
10440
     * creating a SCORM structure if there is one already. However, if the initial SCORM
10441
     * path has been modified, it should use the generic method here below.
10442
     *
10443
     * @return string Returns the zip package string, or null if error
10444
     */
10445
    public function scormExport()
10446
    {
10447
        api_set_more_memory_and_time_limits();
10448
10449
        $_course = api_get_course_info();
10450
        $course_id = $_course['real_id'];
10451
        // Create the zip handler (this will remain available throughout the method).
10452
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
10453
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
10454
        $temp_dir_short = uniqid('scorm_export', true);
10455
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
10456
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
10457
        $zip_folder = new PclZip($temp_zip_file);
10458
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
10459
        $root_path = $main_path = api_get_path(SYS_PATH);
10460
        $files_cleanup = [];
10461
10462
        // Place to temporarily stash the zip file.
10463
        // create the temp dir if it doesn't exist
10464
        // or do a cleanup before creating the zip file.
10465
        if (!is_dir($temp_zip_dir)) {
10466
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
10467
        } else {
10468
            // Cleanup: Check the temp dir for old files and delete them.
10469
            $handle = opendir($temp_zip_dir);
10470
            while (false !== ($file = readdir($handle))) {
10471
                if ($file != '.' && $file != '..') {
10472
                    unlink("$temp_zip_dir/$file");
10473
                }
10474
            }
10475
            closedir($handle);
10476
        }
10477
        $zip_files = $zip_files_abs = $zip_files_dist = [];
10478
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
10479
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
10480
        ) {
10481
            // Remove the possible . at the end of the path.
10482
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
10483
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
10484
            mkdir(
10485
                $dest_path_to_scorm_folder,
10486
                api_get_permissions_for_new_directories(),
10487
                true
10488
            );
10489
            copyr(
10490
                $current_course_path.'/scorm/'.$this->path,
10491
                $dest_path_to_scorm_folder,
10492
                ['imsmanifest'],
10493
                $zip_files
10494
            );
10495
        }
10496
10497
        // Build a dummy imsmanifest structure.
10498
        // Do not add to the zip yet (we still need it).
10499
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
10500
        // Aggregation Model official document, section "2.3 Content Packaging".
10501
        // We are going to build a UTF-8 encoded manifest.
10502
        // Later we will recode it to the desired (and supported) encoding.
10503
        $xmldoc = new DOMDocument('1.0');
10504
        $root = $xmldoc->createElement('manifest');
10505
        $root->setAttribute('identifier', 'SingleCourseManifest');
10506
        $root->setAttribute('version', '1.1');
10507
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
10508
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
10509
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
10510
        $root->setAttribute(
10511
            'xsi:schemaLocation',
10512
            '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'
10513
        );
10514
        // Build mandatory sub-root container elements.
10515
        $metadata = $xmldoc->createElement('metadata');
10516
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
10517
        $metadata->appendChild($md_schema);
10518
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
10519
        $metadata->appendChild($md_schemaversion);
10520
        $root->appendChild($metadata);
10521
10522
        $organizations = $xmldoc->createElement('organizations');
10523
        $resources = $xmldoc->createElement('resources');
10524
10525
        // Build the only organization we will use in building our learnpaths.
10526
        $organizations->setAttribute('default', 'chamilo_scorm_export');
10527
        $organization = $xmldoc->createElement('organization');
10528
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
10529
        // To set the title of the SCORM entity (=organization), we take the name given
10530
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
10531
        // learning path charset) as it is the encoding that defines how it is stored
10532
        // in the database. Then we convert it to HTML entities again as the "&" character
10533
        // alone is not authorized in XML (must be &amp;).
10534
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
10535
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
10536
        $organization->appendChild($org_title);
10537
        $folder_name = 'document';
10538
10539
        // Removes the learning_path/scorm_folder path when exporting see #4841
10540
        $path_to_remove = '';
10541
        $path_to_replace = '';
10542
        $result = $this->generate_lp_folder($_course);
10543
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
10544
            $path_to_remove = 'document'.$result['dir'];
10545
            $path_to_replace = $folder_name.'/';
10546
        }
10547
10548
        // Fixes chamilo scorm exports
10549
        if ($this->ref === 'chamilo_scorm_export') {
10550
            $path_to_remove = 'scorm/'.$this->path.'/document/';
10551
        }
10552
10553
        // For each element, add it to the imsmanifest structure, then add it to the zip.
10554
        $link_updates = [];
10555
        $links_to_create = [];
10556
        foreach ($this->ordered_items as $index => $itemId) {
10557
            /** @var learnpathItem $item */
10558
            $item = $this->items[$itemId];
10559
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
10560
                // Get included documents from this item.
10561
                if ($item->type === 'sco') {
10562
                    $inc_docs = $item->get_resources_from_source(
10563
                        null,
10564
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
10565
                    );
10566
                } else {
10567
                    $inc_docs = $item->get_resources_from_source();
10568
                }
10569
10570
                // Give a child element <item> to the <organization> element.
10571
                $my_item_id = $item->get_id();
10572
                $my_item = $xmldoc->createElement('item');
10573
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
10574
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
10575
                $my_item->setAttribute('isvisible', 'true');
10576
                // Give a child element <title> to the <item> element.
10577
                $my_title = $xmldoc->createElement(
10578
                    'title',
10579
                    htmlspecialchars(
10580
                        api_utf8_encode($item->get_title()),
10581
                        ENT_QUOTES,
10582
                        'UTF-8'
10583
                    )
10584
                );
10585
                $my_item->appendChild($my_title);
10586
                // Give a child element <adlcp:prerequisites> to the <item> element.
10587
                $my_prereqs = $xmldoc->createElement(
10588
                    'adlcp:prerequisites',
10589
                    $this->get_scorm_prereq_string($my_item_id)
10590
                );
10591
                $my_prereqs->setAttribute('type', 'aicc_script');
10592
                $my_item->appendChild($my_prereqs);
10593
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
10594
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
10595
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
10596
                //$xmldoc->createElement('adlcp:timelimitaction','');
10597
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
10598
                //$xmldoc->createElement('adlcp:datafromlms','');
10599
                // Give a child element <adlcp:masteryscore> to the <item> element.
10600
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10601
                $my_item->appendChild($my_masteryscore);
10602
10603
                // Attach this item to the organization element or hits parent if there is one.
10604
                if (!empty($item->parent) && $item->parent != 0) {
10605
                    $children = $organization->childNodes;
10606
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
10607
                    if (is_object($possible_parent)) {
10608
                        $possible_parent->appendChild($my_item);
10609
                    } else {
10610
                        if ($this->debug > 0) {
10611
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
10612
                        }
10613
                    }
10614
                } else {
10615
                    if ($this->debug > 0) {
10616
                        error_log('No parent');
10617
                    }
10618
                    $organization->appendChild($my_item);
10619
                }
10620
10621
                // Get the path of the file(s) from the course directory root.
10622
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
10623
                $my_xml_file_path = $my_file_path;
10624
                if (!empty($path_to_remove)) {
10625
                    // From docs
10626
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
10627
10628
                    // From quiz
10629
                    if ($this->ref === 'chamilo_scorm_export') {
10630
                        $path_to_remove = 'scorm/'.$this->path.'/';
10631
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
10632
                    }
10633
                }
10634
10635
                $my_sub_dir = dirname($my_file_path);
10636
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10637
                $my_xml_sub_dir = $my_sub_dir;
10638
                // Give a <resource> child to the <resources> element
10639
                $my_resource = $xmldoc->createElement('resource');
10640
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10641
                $my_resource->setAttribute('type', 'webcontent');
10642
                $my_resource->setAttribute('href', $my_xml_file_path);
10643
                // adlcp:scormtype can be either 'sco' or 'asset'.
10644
                if ($item->type === 'sco') {
10645
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
10646
                } else {
10647
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
10648
                }
10649
                // xml:base is the base directory to find the files declared in this resource.
10650
                $my_resource->setAttribute('xml:base', '');
10651
                // Give a <file> child to the <resource> element.
10652
                $my_file = $xmldoc->createElement('file');
10653
                $my_file->setAttribute('href', $my_xml_file_path);
10654
                $my_resource->appendChild($my_file);
10655
10656
                // Dependency to other files - not yet supported.
10657
                $i = 1;
10658
                if ($inc_docs) {
10659
                    foreach ($inc_docs as $doc_info) {
10660
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
10661
                            continue;
10662
                        }
10663
                        $my_dep = $xmldoc->createElement('resource');
10664
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
10665
                        $my_dep->setAttribute('identifier', $res_id);
10666
                        $my_dep->setAttribute('type', 'webcontent');
10667
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
10668
                        $my_dep_file = $xmldoc->createElement('file');
10669
                        // Check type of URL.
10670
                        if ($doc_info[1] == 'remote') {
10671
                            // Remote file. Save url as is.
10672
                            $my_dep_file->setAttribute('href', $doc_info[0]);
10673
                            $my_dep->setAttribute('xml:base', '');
10674
                        } elseif ($doc_info[1] === 'local') {
10675
                            switch ($doc_info[2]) {
10676
                                case 'url':
10677
                                    // Local URL - save path as url for now, don't zip file.
10678
                                    $abs_path = api_get_path(SYS_PATH).
10679
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10680
                                    $current_dir = dirname($abs_path);
10681
                                    $current_dir = str_replace('\\', '/', $current_dir);
10682
                                    $file_path = realpath($abs_path);
10683
                                    $file_path = str_replace('\\', '/', $file_path);
10684
                                    $my_dep_file->setAttribute('href', $file_path);
10685
                                    $my_dep->setAttribute('xml:base', '');
10686
                                    if (strstr($file_path, $main_path) !== false) {
10687
                                        // The calculated real path is really inside Chamilo's root path.
10688
                                        // Reduce file path to what's under the DocumentRoot.
10689
                                        $replace = $file_path;
10690
                                        $file_path = substr($file_path, strlen($root_path) - 1);
10691
                                        $destinationFile = $file_path;
10692
10693
                                        if (strstr($file_path, 'upload/users') !== false) {
10694
                                            $pos = strpos($file_path, 'my_files/');
10695
                                            if ($pos !== false) {
10696
                                                $onlyDirectory = str_replace(
10697
                                                    'upload/users/',
10698
                                                    '',
10699
                                                    substr($file_path, $pos, strlen($file_path))
10700
                                                );
10701
                                            }
10702
                                            $replace = $onlyDirectory;
10703
                                            $destinationFile = $replace;
10704
                                        }
10705
                                        $zip_files_abs[] = $file_path;
10706
                                        $link_updates[$my_file_path][] = [
10707
                                            'orig' => $doc_info[0],
10708
                                            'dest' => $destinationFile,
10709
                                            'replace' => $replace,
10710
                                        ];
10711
                                        $my_dep_file->setAttribute('href', $file_path);
10712
                                        $my_dep->setAttribute('xml:base', '');
10713
                                    } elseif (empty($file_path)) {
10714
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
10715
                                        $file_path = str_replace('//', '/', $file_path);
10716
                                        if (file_exists($file_path)) {
10717
                                            // We get the relative path.
10718
                                            $file_path = substr($file_path, strlen($current_dir));
10719
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
10720
                                            $link_updates[$my_file_path][] = [
10721
                                                'orig' => $doc_info[0],
10722
                                                'dest' => $file_path,
10723
                                            ];
10724
                                            $my_dep_file->setAttribute('href', $file_path);
10725
                                            $my_dep->setAttribute('xml:base', '');
10726
                                        }
10727
                                    }
10728
                                    break;
10729
                                case 'abs':
10730
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
10731
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
10732
                                    $my_dep->setAttribute('xml:base', '');
10733
10734
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
10735
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
10736
                                    $abs_img_path_without_subdir = $doc_info[0];
10737
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
10738
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
10739
                                    if ($pos === 0) {
10740
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
10741
                                    }
10742
10743
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
10744
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
10745
10746
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
10747
                                    $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
10748
                                    // Check if the current document is in that path.
10749
                                    if (strstr($file_path, $cur_path) !== false) {
10750
                                        $destinationFile = substr($file_path, strlen($cur_path));
10751
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
10752
10753
                                        $fileToTest = $cur_path.$my_file_path;
10754
                                        if (!empty($path_to_remove)) {
10755
                                            $fileToTest = str_replace(
10756
                                                $path_to_remove.'/',
10757
                                                $path_to_replace,
10758
                                                $cur_path.$my_file_path
10759
                                            );
10760
                                        }
10761
10762
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
10763
10764
                                        // Put the current document in the zip (this array is the array
10765
                                        // that will manage documents already in the course folder - relative).
10766
                                        $zip_files[] = $filePathNoCoursePart;
10767
                                        // Update the links to the current document in the
10768
                                        // containing document (make them relative).
10769
                                        $link_updates[$my_file_path][] = [
10770
                                            'orig' => $doc_info[0],
10771
                                            'dest' => $destinationFile,
10772
                                            'replace' => $relative_path,
10773
                                        ];
10774
10775
                                        $my_dep_file->setAttribute('href', $file_path);
10776
                                        $my_dep->setAttribute('xml:base', '');
10777
                                    } elseif (strstr($file_path, $main_path) !== false) {
10778
                                        // The calculated real path is really inside Chamilo's root path.
10779
                                        // Reduce file path to what's under the DocumentRoot.
10780
                                        $file_path = substr($file_path, strlen($root_path));
10781
                                        $zip_files_abs[] = $file_path;
10782
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10783
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
10784
                                        $my_dep->setAttribute('xml:base', '');
10785
                                    } elseif (empty($file_path)) {
10786
                                        // Probably this is an image inside "/main" directory
10787
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
10788
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10789
10790
                                        if (file_exists($file_path)) {
10791
                                            if (strstr($file_path, 'main/default_course_document') !== false) {
10792
                                                // We get the relative path.
10793
                                                $pos = strpos($file_path, 'main/default_course_document/');
10794
                                                if ($pos !== false) {
10795
                                                    $onlyDirectory = str_replace(
10796
                                                        'main/default_course_document/',
10797
                                                        '',
10798
                                                        substr($file_path, $pos, strlen($file_path))
10799
                                                    );
10800
                                                }
10801
10802
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
10803
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
10804
                                                $zip_files_abs[] = $fileAbs;
10805
                                                $link_updates[$my_file_path][] = [
10806
                                                    'orig' => $doc_info[0],
10807
                                                    'dest' => $destinationFile,
10808
                                                ];
10809
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
10810
                                                $my_dep->setAttribute('xml:base', '');
10811
                                            }
10812
                                        }
10813
                                    }
10814
                                    break;
10815
                                case 'rel':
10816
                                    // Path relative to the current document.
10817
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
10818
                                    if (substr($doc_info[0], 0, 2) === '..') {
10819
                                        // Relative path going up.
10820
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
10821
                                        $current_dir = str_replace('\\', '/', $current_dir);
10822
                                        $file_path = realpath($current_dir.$doc_info[0]);
10823
                                        $file_path = str_replace('\\', '/', $file_path);
10824
                                        if (strstr($file_path, $main_path) !== false) {
10825
                                            // The calculated real path is really inside Chamilo's root path.
10826
                                            // Reduce file path to what's under the DocumentRoot.
10827
                                            $file_path = substr($file_path, strlen($root_path));
10828
                                            $zip_files_abs[] = $file_path;
10829
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10830
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
10831
                                            $my_dep->setAttribute('xml:base', '');
10832
                                        }
10833
                                    } else {
10834
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
10835
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
10836
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
10837
                                    }
10838
                                    break;
10839
                                default:
10840
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
10841
                                    $my_dep->setAttribute('xml:base', '');
10842
                                    break;
10843
                            }
10844
                        }
10845
                        $my_dep->appendChild($my_dep_file);
10846
                        $resources->appendChild($my_dep);
10847
                        $dependency = $xmldoc->createElement('dependency');
10848
                        $dependency->setAttribute('identifierref', $res_id);
10849
                        $my_resource->appendChild($dependency);
10850
                        $i++;
10851
                    }
10852
                }
10853
                $resources->appendChild($my_resource);
10854
                $zip_files[] = $my_file_path;
10855
            } else {
10856
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
10857
                switch ($item->type) {
10858
                    case TOOL_LINK:
10859
                        $my_item = $xmldoc->createElement('item');
10860
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
10861
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
10862
                        $my_item->setAttribute('isvisible', 'true');
10863
                        // Give a child element <title> to the <item> element.
10864
                        $my_title = $xmldoc->createElement(
10865
                            'title',
10866
                            htmlspecialchars(
10867
                                api_utf8_encode($item->get_title()),
10868
                                ENT_QUOTES,
10869
                                'UTF-8'
10870
                            )
10871
                        );
10872
                        $my_item->appendChild($my_title);
10873
                        // Give a child element <adlcp:prerequisites> to the <item> element.
10874
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
10875
                        $my_prereqs->setAttribute('type', 'aicc_script');
10876
                        $my_item->appendChild($my_prereqs);
10877
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
10878
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
10879
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
10880
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
10881
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
10882
                        //$xmldoc->createElement('adlcp:datafromlms', '');
10883
                        // Give a child element <adlcp:masteryscore> to the <item> element.
10884
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10885
                        $my_item->appendChild($my_masteryscore);
10886
10887
                        // Attach this item to the organization element or its parent if there is one.
10888
                        if (!empty($item->parent) && $item->parent != 0) {
10889
                            $children = $organization->childNodes;
10890
                            for ($i = 0; $i < $children->length; $i++) {
10891
                                $item_temp = $children->item($i);
10892
                                if ($item_temp->nodeName == 'item') {
10893
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
10894
                                        $item_temp->appendChild($my_item);
10895
                                    }
10896
                                }
10897
                            }
10898
                        } else {
10899
                            $organization->appendChild($my_item);
10900
                        }
10901
10902
                        $my_file_path = 'link_'.$item->get_id().'.html';
10903
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
10904
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
10905
                        $rs = Database::query($sql);
10906
                        if ($link = Database::fetch_array($rs)) {
10907
                            $url = $link['url'];
10908
                            $title = stripslashes($link['title']);
10909
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
10910
                            $my_xml_file_path = $my_file_path;
10911
                            $my_sub_dir = dirname($my_file_path);
10912
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10913
                            $my_xml_sub_dir = $my_sub_dir;
10914
                            // Give a <resource> child to the <resources> element.
10915
                            $my_resource = $xmldoc->createElement('resource');
10916
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10917
                            $my_resource->setAttribute('type', 'webcontent');
10918
                            $my_resource->setAttribute('href', $my_xml_file_path);
10919
                            // adlcp:scormtype can be either 'sco' or 'asset'.
10920
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
10921
                            // xml:base is the base directory to find the files declared in this resource.
10922
                            $my_resource->setAttribute('xml:base', '');
10923
                            // give a <file> child to the <resource> element.
10924
                            $my_file = $xmldoc->createElement('file');
10925
                            $my_file->setAttribute('href', $my_xml_file_path);
10926
                            $my_resource->appendChild($my_file);
10927
                            $resources->appendChild($my_resource);
10928
                        }
10929
                        break;
10930
                    case TOOL_QUIZ:
10931
                        $exe_id = $item->path;
10932
                        // Should be using ref when everything will be cleaned up in this regard.
10933
                        $exe = new Exercise();
10934
                        $exe->read($exe_id);
10935
                        $my_item = $xmldoc->createElement('item');
10936
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
10937
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
10938
                        $my_item->setAttribute('isvisible', 'true');
10939
                        // Give a child element <title> to the <item> element.
10940
                        $my_title = $xmldoc->createElement(
10941
                            'title',
10942
                            htmlspecialchars(
10943
                                api_utf8_encode($item->get_title()),
10944
                                ENT_QUOTES,
10945
                                'UTF-8'
10946
                            )
10947
                        );
10948
                        $my_item->appendChild($my_title);
10949
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
10950
                        $my_item->appendChild($my_max_score);
10951
                        // Give a child element <adlcp:prerequisites> to the <item> element.
10952
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
10953
                        $my_prereqs->setAttribute('type', 'aicc_script');
10954
                        $my_item->appendChild($my_prereqs);
10955
                        // Give a child element <adlcp:masteryscore> to the <item> element.
10956
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10957
                        $my_item->appendChild($my_masteryscore);
10958
10959
                        // Attach this item to the organization element or hits parent if there is one.
10960
                        if (!empty($item->parent) && $item->parent != 0) {
10961
                            $children = $organization->childNodes;
10962
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
10963
                            if ($possible_parent) {
10964
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
10965
                                    $possible_parent->appendChild($my_item);
10966
                                }
10967
                            }
10968
                        } else {
10969
                            $organization->appendChild($my_item);
10970
                        }
10971
10972
                        // Get the path of the file(s) from the course directory root
10973
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
10974
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
10975
                        // Write the contents of the exported exercise into a (big) html file
10976
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
10977
                        $scormExercise = new ScormExercise($exe, true);
10978
                        $contents = $scormExercise->export();
10979
10980
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
10981
                        $res = file_put_contents($tmp_file_path, $contents);
10982
                        if ($res === false) {
10983
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
10984
                        }
10985
                        $files_cleanup[] = $tmp_file_path;
10986
                        $my_xml_file_path = $my_file_path;
10987
                        $my_sub_dir = dirname($my_file_path);
10988
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10989
                        $my_xml_sub_dir = $my_sub_dir;
10990
                        // Give a <resource> child to the <resources> element.
10991
                        $my_resource = $xmldoc->createElement('resource');
10992
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10993
                        $my_resource->setAttribute('type', 'webcontent');
10994
                        $my_resource->setAttribute('href', $my_xml_file_path);
10995
                        // adlcp:scormtype can be either 'sco' or 'asset'.
10996
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
10997
                        // xml:base is the base directory to find the files declared in this resource.
10998
                        $my_resource->setAttribute('xml:base', '');
10999
                        // Give a <file> child to the <resource> element.
11000
                        $my_file = $xmldoc->createElement('file');
11001
                        $my_file->setAttribute('href', $my_xml_file_path);
11002
                        $my_resource->appendChild($my_file);
11003
11004
                        // Get included docs.
11005
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
11006
11007
                        // Dependency to other files - not yet supported.
11008
                        $i = 1;
11009
                        foreach ($inc_docs as $doc_info) {
11010
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
11011
                                continue;
11012
                            }
11013
                            $my_dep = $xmldoc->createElement('resource');
11014
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11015
                            $my_dep->setAttribute('identifier', $res_id);
11016
                            $my_dep->setAttribute('type', 'webcontent');
11017
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
11018
                            $my_dep_file = $xmldoc->createElement('file');
11019
                            // Check type of URL.
11020
                            if ($doc_info[1] == 'remote') {
11021
                                // Remote file. Save url as is.
11022
                                $my_dep_file->setAttribute('href', $doc_info[0]);
11023
                                $my_dep->setAttribute('xml:base', '');
11024
                            } elseif ($doc_info[1] == 'local') {
11025
                                switch ($doc_info[2]) {
11026
                                    case 'url': // Local URL - save path as url for now, don't zip file.
11027
                                        // Save file but as local file (retrieve from URL).
11028
                                        $abs_path = api_get_path(SYS_PATH).
11029
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11030
                                        $current_dir = dirname($abs_path);
11031
                                        $current_dir = str_replace('\\', '/', $current_dir);
11032
                                        $file_path = realpath($abs_path);
11033
                                        $file_path = str_replace('\\', '/', $file_path);
11034
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11035
                                        $my_dep->setAttribute('xml:base', '');
11036
                                        if (strstr($file_path, $main_path) !== false) {
11037
                                            // The calculated real path is really inside the chamilo root path.
11038
                                            // Reduce file path to what's under the DocumentRoot.
11039
                                            $file_path = substr($file_path, strlen($root_path));
11040
                                            $zip_files_abs[] = $file_path;
11041
                                            $link_updates[$my_file_path][] = [
11042
                                                'orig' => $doc_info[0],
11043
                                                'dest' => 'document/'.$file_path,
11044
                                            ];
11045
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11046
                                            $my_dep->setAttribute('xml:base', '');
11047
                                        } elseif (empty($file_path)) {
11048
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11049
                                            $file_path = str_replace('//', '/', $file_path);
11050
                                            if (file_exists($file_path)) {
11051
                                                $file_path = substr($file_path, strlen($current_dir));
11052
                                                // We get the relative path.
11053
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11054
                                                $link_updates[$my_file_path][] = [
11055
                                                    'orig' => $doc_info[0],
11056
                                                    'dest' => 'document/'.$file_path,
11057
                                                ];
11058
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11059
                                                $my_dep->setAttribute('xml:base', '');
11060
                                            }
11061
                                        }
11062
                                        break;
11063
                                    case 'abs':
11064
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11065
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11066
                                        $current_dir = str_replace('\\', '/', $current_dir);
11067
                                        $file_path = realpath($doc_info[0]);
11068
                                        $file_path = str_replace('\\', '/', $file_path);
11069
                                        $my_dep_file->setAttribute('href', $file_path);
11070
                                        $my_dep->setAttribute('xml:base', '');
11071
11072
                                        if (strstr($file_path, $main_path) !== false) {
11073
                                            // The calculated real path is really inside the chamilo root path.
11074
                                            // Reduce file path to what's under the DocumentRoot.
11075
                                            $file_path = substr($file_path, strlen($root_path));
11076
                                            $zip_files_abs[] = $file_path;
11077
                                            $link_updates[$my_file_path][] = [
11078
                                                'orig' => $doc_info[0],
11079
                                                'dest' => $file_path,
11080
                                            ];
11081
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11082
                                            $my_dep->setAttribute('xml:base', '');
11083
                                        } elseif (empty($file_path)) {
11084
                                            $docSysPartPath = str_replace(
11085
                                                api_get_path(REL_COURSE_PATH),
11086
                                                '',
11087
                                                $doc_info[0]
11088
                                            );
11089
11090
                                            $docSysPartPathNoCourseCode = str_replace(
11091
                                                $_course['directory'].'/',
11092
                                                '',
11093
                                                $docSysPartPath
11094
                                            );
11095
11096
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
11097
                                            if (file_exists($docSysPath)) {
11098
                                                $file_path = $docSysPartPathNoCourseCode;
11099
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11100
                                                $link_updates[$my_file_path][] = [
11101
                                                    'orig' => $doc_info[0],
11102
                                                    'dest' => $file_path,
11103
                                                ];
11104
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11105
                                                $my_dep->setAttribute('xml:base', '');
11106
                                            }
11107
                                        }
11108
                                        break;
11109
                                    case 'rel':
11110
                                        // Path relative to the current document. Save xml:base as current document's
11111
                                        // directory and save file in zip as subdir.file_path
11112
                                        if (substr($doc_info[0], 0, 2) === '..') {
11113
                                            // Relative path going up.
11114
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11115
                                            $current_dir = str_replace('\\', '/', $current_dir);
11116
                                            $file_path = realpath($current_dir.$doc_info[0]);
11117
                                            $file_path = str_replace('\\', '/', $file_path);
11118
                                            if (strstr($file_path, $main_path) !== false) {
11119
                                                // The calculated real path is really inside Chamilo's root path.
11120
                                                // Reduce file path to what's under the DocumentRoot.
11121
11122
                                                $file_path = substr($file_path, strlen($root_path));
11123
                                                $file_path_dest = $file_path;
11124
11125
                                                // File path is courses/CHAMILO/document/....
11126
                                                $info_file_path = explode('/', $file_path);
11127
                                                if ($info_file_path[0] == 'courses') {
11128
                                                    // Add character "/" in file path.
11129
                                                    $file_path_dest = 'document/'.$file_path;
11130
                                                }
11131
                                                $zip_files_abs[] = $file_path;
11132
11133
                                                $link_updates[$my_file_path][] = [
11134
                                                    'orig' => $doc_info[0],
11135
                                                    'dest' => $file_path_dest,
11136
                                                ];
11137
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11138
                                                $my_dep->setAttribute('xml:base', '');
11139
                                            }
11140
                                        } else {
11141
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11142
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
11143
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11144
                                        }
11145
                                        break;
11146
                                    default:
11147
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
11148
                                        $my_dep->setAttribute('xml:base', '');
11149
                                        break;
11150
                                }
11151
                            }
11152
                            $my_dep->appendChild($my_dep_file);
11153
                            $resources->appendChild($my_dep);
11154
                            $dependency = $xmldoc->createElement('dependency');
11155
                            $dependency->setAttribute('identifierref', $res_id);
11156
                            $my_resource->appendChild($dependency);
11157
                            $i++;
11158
                        }
11159
                        $resources->appendChild($my_resource);
11160
                        $zip_files[] = $my_file_path;
11161
                        break;
11162
                    default:
11163
                        // Get the path of the file(s) from the course directory root
11164
                        $my_file_path = 'non_exportable.html';
11165
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
11166
                        $my_xml_file_path = $my_file_path;
11167
                        $my_sub_dir = dirname($my_file_path);
11168
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11169
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
11170
                        $my_xml_sub_dir = $my_sub_dir;
11171
                        // Give a <resource> child to the <resources> element.
11172
                        $my_resource = $xmldoc->createElement('resource');
11173
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11174
                        $my_resource->setAttribute('type', 'webcontent');
11175
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
11176
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11177
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
11178
                        // xml:base is the base directory to find the files declared in this resource.
11179
                        $my_resource->setAttribute('xml:base', '');
11180
                        // Give a <file> child to the <resource> element.
11181
                        $my_file = $xmldoc->createElement('file');
11182
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
11183
                        $my_resource->appendChild($my_file);
11184
                        $resources->appendChild($my_resource);
11185
                        break;
11186
                }
11187
            }
11188
        }
11189
        $organizations->appendChild($organization);
11190
        $root->appendChild($organizations);
11191
        $root->appendChild($resources);
11192
        $xmldoc->appendChild($root);
11193
11194
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
11195
11196
        // then add the file to the zip, then destroy the file (this is done automatically).
11197
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
11198
        foreach ($zip_files as $file_path) {
11199
            if (empty($file_path)) {
11200
                continue;
11201
            }
11202
11203
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
11204
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
11205
11206
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
11207
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
11208
            }
11209
11210
            $this->create_path($dest_file);
11211
            @copy($filePath, $dest_file);
11212
11213
            // Check if the file needs a link update.
11214
            if (in_array($file_path, array_keys($link_updates))) {
11215
                $string = file_get_contents($dest_file);
11216
                unlink($dest_file);
11217
                foreach ($link_updates[$file_path] as $old_new) {
11218
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11219
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11220
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11221
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11222
                    if (substr($old_new['dest'], -3) === 'flv' &&
11223
                        substr($old_new['dest'], 0, 5) === 'main/'
11224
                    ) {
11225
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11226
                    } elseif (substr($old_new['dest'], -3) === 'flv' &&
11227
                        substr($old_new['dest'], 0, 6) === 'video/'
11228
                    ) {
11229
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
11230
                    }
11231
11232
                    // Fix to avoid problems with default_course_document
11233
                    if (strpos('main/default_course_document', $old_new['dest']) === false) {
11234
                        $newDestination = $old_new['dest'];
11235
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
11236
                            $newDestination = $old_new['replace'];
11237
                        }
11238
                    } else {
11239
                        $newDestination = str_replace('document/', '', $old_new['dest']);
11240
                    }
11241
                    $string = str_replace($old_new['orig'], $newDestination, $string);
11242
11243
                    // Add files inside the HTMLs
11244
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
11245
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
11246
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
11247
                        copy(
11248
                            $sys_course_path.$new_path,
11249
                            $destinationFile
11250
                        );
11251
                    }
11252
                }
11253
                file_put_contents($dest_file, $string);
11254
            }
11255
11256
            if (file_exists($filePath) && $copyAll) {
11257
                $extension = $this->get_extension($filePath);
11258
                if (in_array($extension, ['html', 'html'])) {
11259
                    $containerOrigin = dirname($filePath);
11260
                    $containerDestination = dirname($dest_file);
11261
11262
                    $finder = new Finder();
11263
                    $finder->files()->in($containerOrigin)
11264
                        ->notName('*_DELETED_*')
11265
                        ->exclude('share_folder')
11266
                        ->exclude('chat_files')
11267
                        ->exclude('certificates')
11268
                    ;
11269
11270
                    if (is_dir($containerOrigin) &&
11271
                        is_dir($containerDestination)
11272
                    ) {
11273
                        $fs = new Filesystem();
11274
                        $fs->mirror(
11275
                            $containerOrigin,
11276
                            $containerDestination,
11277
                            $finder
11278
                        );
11279
                    }
11280
                }
11281
            }
11282
        }
11283
11284
        foreach ($zip_files_abs as $file_path) {
11285
            if (empty($file_path)) {
11286
                continue;
11287
            }
11288
11289
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
11290
                continue;
11291
            }
11292
11293
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
11294
            if (strstr($file_path, 'upload/users') !== false) {
11295
                $pos = strpos($file_path, 'my_files/');
11296
                if ($pos !== false) {
11297
                    $onlyDirectory = str_replace(
11298
                        'upload/users/',
11299
                        '',
11300
                        substr($file_path, $pos, strlen($file_path))
11301
                    );
11302
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
11303
                }
11304
            }
11305
11306
            if (strstr($file_path, 'default_course_document/') !== false) {
11307
                $replace = str_replace('/main', '', $file_path);
11308
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
11309
            }
11310
11311
            if (empty($dest_file)) {
11312
                continue;
11313
            }
11314
11315
            $this->create_path($dest_file);
11316
            copy($main_path.$file_path, $dest_file);
11317
            // Check if the file needs a link update.
11318
            if (in_array($file_path, array_keys($link_updates))) {
11319
                $string = file_get_contents($dest_file);
11320
                unlink($dest_file);
11321
                foreach ($link_updates[$file_path] as $old_new) {
11322
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11323
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11324
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11325
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11326
                    if (substr($old_new['dest'], -3) == 'flv' &&
11327
                        substr($old_new['dest'], 0, 5) == 'main/'
11328
                    ) {
11329
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11330
                    }
11331
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
11332
                }
11333
                file_put_contents($dest_file, $string);
11334
            }
11335
        }
11336
11337
        if (is_array($links_to_create)) {
11338
            foreach ($links_to_create as $file => $link) {
11339
                $content = '<!DOCTYPE html><head>
11340
                            <meta charset="'.api_get_language_isocode().'" />
11341
                            <title>'.$link['title'].'</title>
11342
                            </head>
11343
                            <body dir="'.api_get_text_direction().'">
11344
                            <div style="text-align:center">
11345
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
11346
                            </body>
11347
                            </html>';
11348
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
11349
            }
11350
        }
11351
11352
        // Add non exportable message explanation.
11353
        $lang_not_exportable = get_lang('This learning object or activity is not SCORM compliant. That\'s why it is not exportable.');
11354
        $file_content = '<!DOCTYPE html><head>
11355
                        <meta charset="'.api_get_language_isocode().'" />
11356
                        <title>'.$lang_not_exportable.'</title>
11357
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
11358
                        </head>
11359
                        <body dir="'.api_get_text_direction().'">';
11360
        $file_content .=
11361
            <<<EOD
11362
                    <style>
11363
            .error-message {
11364
                font-family: arial, verdana, helvetica, sans-serif;
11365
                border-width: 1px;
11366
                border-style: solid;
11367
                left: 50%;
11368
                margin: 10px auto;
11369
                min-height: 30px;
11370
                padding: 5px;
11371
                right: 50%;
11372
                width: 500px;
11373
                background-color: #FFD1D1;
11374
                border-color: #FF0000;
11375
                color: #000;
11376
            }
11377
        </style>
11378
    <body>
11379
        <div class="error-message">
11380
            $lang_not_exportable
11381
        </div>
11382
    </body>
11383
</html>
11384
EOD;
11385
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
11386
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
11387
        }
11388
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
11389
11390
        // Add the extra files that go along with a SCORM package.
11391
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
11392
11393
        $fs = new Filesystem();
11394
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
11395
11396
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
11397
        $manifest = @$xmldoc->saveXML();
11398
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
11399
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
11400
        $zip_folder->add(
11401
            $archivePath.'/'.$temp_dir_short,
11402
            PCLZIP_OPT_REMOVE_PATH,
11403
            $archivePath.'/'.$temp_dir_short.'/'
11404
        );
11405
11406
        // Clean possible temporary files.
11407
        foreach ($files_cleanup as $file) {
11408
            $res = unlink($file);
11409
            if ($res === false) {
11410
                error_log(
11411
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
11412
                    0
11413
                );
11414
            }
11415
        }
11416
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
11417
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
11418
    }
11419
11420
    /**
11421
     * @param int $lp_id
11422
     *
11423
     * @return bool
11424
     */
11425
    public function scorm_export_to_pdf($lp_id)
11426
    {
11427
        $lp_id = (int) $lp_id;
11428
        $files_to_export = [];
11429
11430
        $sessionId = api_get_session_id();
11431
        $course_data = api_get_course_info($this->cc);
11432
11433
        if (!empty($course_data)) {
11434
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
11435
            $list = self::get_flat_ordered_items_list($lp_id);
11436
            if (!empty($list)) {
11437
                foreach ($list as $item_id) {
11438
                    $item = $this->items[$item_id];
11439
                    switch ($item->type) {
11440
                        case 'document':
11441
                            // Getting documents from a LP with chamilo documents
11442
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
11443
                            // Try loading document from the base course.
11444
                            if (empty($file_data) && !empty($sessionId)) {
11445
                                $file_data = DocumentManager::get_document_data_by_id(
11446
                                    $item->path,
11447
                                    $this->cc,
11448
                                    false,
11449
                                    0
11450
                                );
11451
                            }
11452
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
11453
                            if (file_exists($file_path)) {
11454
                                $files_to_export[] = [
11455
                                    'title' => $item->get_title(),
11456
                                    'path' => $file_path,
11457
                                ];
11458
                            }
11459
                            break;
11460
                        case 'asset': //commes from a scorm package generated by chamilo
11461
                        case 'sco':
11462
                            $file_path = $scorm_path.'/'.$item->path;
11463
                            if (file_exists($file_path)) {
11464
                                $files_to_export[] = [
11465
                                    'title' => $item->get_title(),
11466
                                    'path' => $file_path,
11467
                                ];
11468
                            }
11469
                            break;
11470
                        case 'dir':
11471
                            $files_to_export[] = [
11472
                                'title' => $item->get_title(),
11473
                                'path' => null,
11474
                            ];
11475
                            break;
11476
                    }
11477
                }
11478
            }
11479
11480
            $pdf = new PDF();
11481
            $result = $pdf->html_to_pdf(
11482
                $files_to_export,
11483
                $this->name,
11484
                $this->cc,
11485
                true,
11486
                true,
11487
                true,
11488
                $this->get_name()
11489
            );
11490
11491
            return $result;
11492
        }
11493
11494
        return false;
11495
    }
11496
11497
    /**
11498
     * Temp function to be moved in main_api or the best place around for this.
11499
     * Creates a file path if it doesn't exist.
11500
     *
11501
     * @param string $path
11502
     */
11503
    public function create_path($path)
11504
    {
11505
        $path_bits = explode('/', dirname($path));
11506
11507
        // IS_WINDOWS_OS has been defined in main_api.lib.php
11508
        $path_built = IS_WINDOWS_OS ? '' : '/';
11509
        foreach ($path_bits as $bit) {
11510
            if (!empty($bit)) {
11511
                $new_path = $path_built.$bit;
11512
                if (is_dir($new_path)) {
11513
                    $path_built = $new_path.'/';
11514
                } else {
11515
                    mkdir($new_path, api_get_permissions_for_new_directories());
11516
                    $path_built = $new_path.'/';
11517
                }
11518
            }
11519
        }
11520
    }
11521
11522
    /**
11523
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
11524
     *
11525
     * @return bool The results of the unlink function, or false if there was no image to start with
11526
     */
11527
    public function delete_lp_image()
11528
    {
11529
        $img = $this->get_preview_image();
11530
        if ($img != '') {
11531
            $del_file = $this->get_preview_image_path(null, 'sys');
11532
            if (isset($del_file) && file_exists($del_file)) {
11533
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
11534
                if (file_exists($del_file_2)) {
11535
                    unlink($del_file_2);
11536
                }
11537
                $this->set_preview_image('');
11538
11539
                return @unlink($del_file);
11540
            }
11541
        }
11542
11543
        return false;
11544
    }
11545
11546
    /**
11547
     * Uploads an author image to the upload/learning_path/images directory.
11548
     *
11549
     * @param array    The image array, coming from the $_FILES superglobal
11550
     *
11551
     * @return bool True on success, false on error
11552
     */
11553
    public function upload_image($image_array)
11554
    {
11555
        if (!empty($image_array['name'])) {
11556
            $upload_ok = process_uploaded_file($image_array);
11557
            $has_attachment = true;
11558
        }
11559
11560
        if ($upload_ok && $has_attachment) {
11561
            $courseDir = api_get_course_path().'/upload/learning_path/images';
11562
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
11563
            $updir = $sys_course_path.$courseDir;
11564
            // Try to add an extension to the file if it hasn't one.
11565
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
11566
11567
            if (filter_extension($new_file_name)) {
11568
                $file_extension = explode('.', $image_array['name']);
11569
                $file_extension = strtolower($file_extension[count($file_extension) - 1]);
11570
                $filename = uniqid('');
11571
                $new_file_name = $filename.'.'.$file_extension;
11572
                $new_path = $updir.'/'.$new_file_name;
11573
11574
                // Resize the image.
11575
                $temp = new Image($image_array['tmp_name']);
11576
                $temp->resize(104);
11577
                $result = $temp->send_image($new_path);
11578
11579
                // Storing the image filename.
11580
                if ($result) {
11581
                    $this->set_preview_image($new_file_name);
11582
11583
                    //Resize to 64px to use on course homepage
11584
                    $temp->resize(64);
11585
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
11586
11587
                    return true;
11588
                }
11589
            }
11590
        }
11591
11592
        return false;
11593
    }
11594
11595
    /**
11596
     * @param int    $lp_id
11597
     * @param string $status
11598
     */
11599
    public function set_autolaunch($lp_id, $status)
11600
    {
11601
        $course_id = api_get_course_int_id();
11602
        $lp_id = (int) $lp_id;
11603
        $status = (int) $status;
11604
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
11605
11606
        // Setting everything to autolaunch = 0
11607
        $attributes['autolaunch'] = 0;
11608
        $where = [
11609
            'session_id = ? AND c_id = ? ' => [
11610
                api_get_session_id(),
11611
                $course_id,
11612
            ],
11613
        ];
11614
        Database::update($lp_table, $attributes, $where);
11615
        if ($status == 1) {
11616
            //Setting my lp_id to autolaunch = 1
11617
            $attributes['autolaunch'] = 1;
11618
            $where = [
11619
                'iid = ? AND session_id = ? AND c_id = ?' => [
11620
                    $lp_id,
11621
                    api_get_session_id(),
11622
                    $course_id,
11623
                ],
11624
            ];
11625
            Database::update($lp_table, $attributes, $where);
11626
        }
11627
    }
11628
11629
    /**
11630
     * Gets previous_item_id for the next element of the lp_item table.
11631
     *
11632
     * @author Isaac flores paz
11633
     *
11634
     * @return int Previous item ID
11635
     */
11636
    public function select_previous_item_id()
11637
    {
11638
        $course_id = api_get_course_int_id();
11639
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11640
11641
        // Get the max order of the items
11642
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
11643
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
11644
        $rs_max_order = Database::query($sql);
11645
        $row_max_order = Database::fetch_object($rs_max_order);
11646
        $max_order = $row_max_order->display_order;
11647
        // Get the previous item ID
11648
        $sql = "SELECT iid as previous FROM $table_lp_item
11649
                WHERE
11650
                    c_id = $course_id AND
11651
                    lp_id = ".$this->lp_id." AND
11652
                    display_order = '$max_order' ";
11653
        $rs_max = Database::query($sql);
11654
        $row_max = Database::fetch_object($rs_max);
11655
11656
        // Return the previous item ID
11657
        return $row_max->previous;
11658
    }
11659
11660
    /**
11661
     * Copies an LP.
11662
     */
11663
    public function copy()
11664
    {
11665
        // Course builder
11666
        $cb = new CourseBuilder();
11667
11668
        //Setting tools that will be copied
11669
        $cb->set_tools_to_build(['learnpaths']);
11670
11671
        //Setting elements that will be copied
11672
        $cb->set_tools_specific_id_list(
11673
            ['learnpaths' => [$this->lp_id]]
11674
        );
11675
11676
        $course = $cb->build();
11677
11678
        //Course restorer
11679
        $course_restorer = new CourseRestorer($course);
11680
        $course_restorer->set_add_text_in_items(true);
11681
        $course_restorer->set_tool_copy_settings(
11682
            ['learnpaths' => ['reset_dates' => true]]
11683
        );
11684
        $course_restorer->restore(
11685
            api_get_course_id(),
11686
            api_get_session_id(),
11687
            false,
11688
            false
11689
        );
11690
    }
11691
11692
    /**
11693
     * Verify document size.
11694
     *
11695
     * @param string $s
11696
     *
11697
     * @return bool
11698
     */
11699
    public static function verify_document_size($s)
11700
    {
11701
        $post_max = ini_get('post_max_size');
11702
        if (substr($post_max, -1, 1) == 'M') {
11703
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
11704
        } elseif (substr($post_max, -1, 1) == 'G') {
11705
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
11706
        }
11707
        $upl_max = ini_get('upload_max_filesize');
11708
        if (substr($upl_max, -1, 1) == 'M') {
11709
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
11710
        } elseif (substr($upl_max, -1, 1) == 'G') {
11711
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
11712
        }
11713
11714
        $repo = Container::getDocumentRepository();
11715
        $documents_total_space = $repo->getTotalSpace(api_get_course_int_id());
11716
11717
        $course_max_space = DocumentManager::get_course_quota();
11718
        $total_size = filesize($s) + $documents_total_space;
11719
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
11720
            return true;
11721
        }
11722
11723
        return false;
11724
    }
11725
11726
    /**
11727
     * Clear LP prerequisites.
11728
     */
11729
    public function clear_prerequisites()
11730
    {
11731
        $course_id = $this->get_course_int_id();
11732
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11733
        $lp_id = $this->get_id();
11734
        // Cleaning prerequisites
11735
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
11736
                WHERE c_id = $course_id AND lp_id = $lp_id";
11737
        Database::query($sql);
11738
11739
        // Cleaning mastery score for exercises
11740
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
11741
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
11742
        Database::query($sql);
11743
    }
11744
11745
    public function set_previous_step_as_prerequisite_for_all_items()
11746
    {
11747
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11748
        $course_id = $this->get_course_int_id();
11749
        $lp_id = $this->get_id();
11750
11751
        if (!empty($this->items)) {
11752
            $previous_item_id = null;
11753
            $previous_item_max = 0;
11754
            $previous_item_type = null;
11755
            $last_item_not_dir = null;
11756
            $last_item_not_dir_type = null;
11757
            $last_item_not_dir_max = null;
11758
11759
            foreach ($this->ordered_items as $itemId) {
11760
                $item = $this->getItem($itemId);
11761
                // if there was a previous item... (otherwise jump to set it)
11762
                if (!empty($previous_item_id)) {
11763
                    $current_item_id = $item->get_id(); //save current id
11764
                    if ($item->get_type() != 'dir') {
11765
                        // Current item is not a folder, so it qualifies to get a prerequisites
11766
                        if ($last_item_not_dir_type == 'quiz') {
11767
                            // if previous is quiz, mark its max score as default score to be achieved
11768
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
11769
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
11770
                            Database::query($sql);
11771
                        }
11772
                        // now simply update the prerequisite to set it to the last non-chapter item
11773
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
11774
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
11775
                        Database::query($sql);
11776
                        // record item as 'non-chapter' reference
11777
                        $last_item_not_dir = $item->get_id();
11778
                        $last_item_not_dir_type = $item->get_type();
11779
                        $last_item_not_dir_max = $item->get_max();
11780
                    }
11781
                } else {
11782
                    if ($item->get_type() != 'dir') {
11783
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
11784
                        $last_item_not_dir = $item->get_id();
11785
                        $last_item_not_dir_type = $item->get_type();
11786
                        $last_item_not_dir_max = $item->get_max();
11787
                    }
11788
                }
11789
                // Saving the item as "previous item" for the next loop
11790
                $previous_item_id = $item->get_id();
11791
                $previous_item_max = $item->get_max();
11792
                $previous_item_type = $item->get_type();
11793
            }
11794
        }
11795
    }
11796
11797
    /**
11798
     * @param array $params
11799
     *
11800
     * @throws \Doctrine\ORM\OptimisticLockException
11801
     *
11802
     * @return int
11803
     */
11804
    public static function createCategory($params)
11805
    {
11806
        $item = new CLpCategory();
11807
        $item->setName($params['name']);
11808
        $item->setCId($params['c_id']);
11809
11810
        $repo = Container::getLpCategoryRepository();
11811
        $em = $repo->getEntityManager();
11812
        $em->persist($item);
11813
        $courseEntity = api_get_course_entity(api_get_course_int_id());
11814
11815
        $repo->addResourceToCourse(
11816
            $item,
11817
            ResourceLink::VISIBILITY_PUBLISHED,
11818
            api_get_user_entity(api_get_user_id()),
11819
            $courseEntity,
11820
            api_get_session_entity(),
11821
            api_get_group_entity()
11822
        );
11823
11824
        $em->flush();
11825
11826
        /*api_item_property_update(
11827
            api_get_course_info(),
11828
            TOOL_LEARNPATH_CATEGORY,
11829
            $item->getId(),
11830
            'visible',
11831
            api_get_user_id()
11832
        );*/
11833
11834
        return $item->getId();
11835
    }
11836
11837
    /**
11838
     * @param array $params
11839
     *
11840
     * @throws \Doctrine\ORM\ORMException
11841
     * @throws \Doctrine\ORM\OptimisticLockException
11842
     * @throws \Doctrine\ORM\TransactionRequiredException
11843
     */
11844
    public static function updateCategory($params)
11845
    {
11846
        $em = Database::getManager();
11847
        /** @var CLpCategory $item */
11848
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
11849
        if ($item) {
11850
            $item->setName($params['name']);
11851
            $em->merge($item);
11852
            $em->flush();
11853
        }
11854
    }
11855
11856
    /**
11857
     * @param int $id
11858
     *
11859
     * @throws \Doctrine\ORM\ORMException
11860
     * @throws \Doctrine\ORM\OptimisticLockException
11861
     * @throws \Doctrine\ORM\TransactionRequiredException
11862
     */
11863
    public static function moveUpCategory($id)
11864
    {
11865
        $id = (int) $id;
11866
        $em = Database::getManager();
11867
        /** @var CLpCategory $item */
11868
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11869
        if ($item) {
11870
            $position = $item->getPosition() - 1;
11871
            $item->setPosition($position);
11872
            $em->persist($item);
11873
            $em->flush();
11874
        }
11875
    }
11876
11877
    /**
11878
     * @param int $id
11879
     *
11880
     * @throws \Doctrine\ORM\ORMException
11881
     * @throws \Doctrine\ORM\OptimisticLockException
11882
     * @throws \Doctrine\ORM\TransactionRequiredException
11883
     */
11884
    public static function moveDownCategory($id)
11885
    {
11886
        $id = (int) $id;
11887
        $em = Database::getManager();
11888
        /** @var CLpCategory $item */
11889
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11890
        if ($item) {
11891
            $position = $item->getPosition() + 1;
11892
            $item->setPosition($position);
11893
            $em->persist($item);
11894
            $em->flush();
11895
        }
11896
    }
11897
11898
    /**
11899
     * @param int $courseId
11900
     *
11901
     * @throws \Doctrine\ORM\Query\QueryException
11902
     *
11903
     * @return int|mixed
11904
     */
11905
    public static function getCountCategories($courseId)
11906
    {
11907
        if (empty($courseId)) {
11908
            return 0;
11909
        }
11910
        $em = Database::getManager();
11911
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
11912
        $query->setParameter('id', $courseId);
11913
11914
        return $query->getSingleScalarResult();
11915
    }
11916
11917
    /**
11918
     * @param int $courseId
11919
     *
11920
     * @return mixed
11921
     */
11922
    public static function getCategories($courseId)
11923
    {
11924
        $em = Database::getManager();
11925
11926
        // Using doctrine extensions
11927
        /** @var SortableRepository $repo */
11928
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
11929
        $items = $repo
11930
            ->getBySortableGroupsQuery(['cId' => $courseId])
11931
            ->getResult();
11932
11933
        return $items;
11934
    }
11935
11936
    /**
11937
     * @param int $id
11938
     *
11939
     * @throws \Doctrine\ORM\ORMException
11940
     * @throws \Doctrine\ORM\OptimisticLockException
11941
     * @throws \Doctrine\ORM\TransactionRequiredException
11942
     *
11943
     * @return CLpCategory
11944
     */
11945
    public static function getCategory($id)
11946
    {
11947
        $id = (int) $id;
11948
        $em = Database::getManager();
11949
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11950
11951
        return $item;
11952
    }
11953
11954
    /**
11955
     * @param int $courseId
11956
     *
11957
     * @return array
11958
     */
11959
    public static function getCategoryByCourse($courseId)
11960
    {
11961
        $em = Database::getManager();
11962
        $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
11963
            ['cId' => $courseId]
11964
        );
11965
11966
        return $items;
11967
    }
11968
11969
    /**
11970
     * @param int $id
11971
     *
11972
     * @throws \Doctrine\ORM\ORMException
11973
     * @throws \Doctrine\ORM\OptimisticLockException
11974
     * @throws \Doctrine\ORM\TransactionRequiredException
11975
     *
11976
     * @return mixed
11977
     */
11978
    public static function deleteCategory($id)
11979
    {
11980
        $em = Database::getManager();
11981
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11982
        if ($item) {
11983
            $courseId = $item->getCId();
11984
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
11985
            $query->setParameter('id', $courseId);
11986
            $query->setParameter('catId', $item->getId());
11987
            $lps = $query->getResult();
11988
11989
            // Setting category = 0.
11990
            if ($lps) {
11991
                foreach ($lps as $lpItem) {
11992
                    $lpItem->setCategoryId(0);
11993
                }
11994
            }
11995
11996
            // Removing category.
11997
            $em->remove($item);
11998
            $em->flush();
11999
12000
            $courseInfo = api_get_course_info_by_id($courseId);
12001
            $sessionId = api_get_session_id();
12002
12003
            // Delete link tool
12004
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
12005
            $link = 'lp/lp_controller.php?cid='.$courseInfo['real_id'].'&sid='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
12006
            // Delete tools
12007
            $sql = "DELETE FROM $tbl_tool
12008
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
12009
            Database::query($sql);
12010
12011
            return true;
12012
        }
12013
12014
        return false;
12015
    }
12016
12017
    /**
12018
     * @param int  $courseId
12019
     * @param bool $addSelectOption
12020
     *
12021
     * @return mixed
12022
     */
12023
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
12024
    {
12025
        $items = self::getCategoryByCourse($courseId);
12026
        $cats = [];
12027
        if ($addSelectOption) {
12028
            $cats = [get_lang('Select a category')];
12029
        }
12030
12031
        if (!empty($items)) {
12032
            foreach ($items as $cat) {
12033
                $cats[$cat->getId()] = $cat->getName();
12034
            }
12035
        }
12036
12037
        return $cats;
12038
    }
12039
12040
    /**
12041
     * @param string $courseCode
12042
     * @param int    $lpId
12043
     * @param int    $user_id
12044
     *
12045
     * @return learnpath
12046
     */
12047
    public static function getLpFromSession($courseCode, $lpId, $user_id)
12048
    {
12049
        $debug = 0;
12050
        $learnPath = null;
12051
        $lpObject = Session::read('lpobject');
12052
        if ($lpObject !== null) {
12053
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
12054
            if ($debug) {
12055
                error_log('getLpFromSession: unserialize');
12056
                error_log('------getLpFromSession------');
12057
                error_log('------unserialize------');
12058
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12059
                error_log("api_get_sessionid: ".api_get_session_id());
12060
            }
12061
        }
12062
12063
        if (!is_object($learnPath)) {
12064
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
12065
            if ($debug) {
12066
                error_log('------getLpFromSession------');
12067
                error_log('getLpFromSession: create new learnpath');
12068
                error_log("create new LP with $courseCode - $lpId - $user_id");
12069
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12070
                error_log("api_get_sessionid: ".api_get_session_id());
12071
            }
12072
        }
12073
12074
        return $learnPath;
12075
    }
12076
12077
    /**
12078
     * @param int $itemId
12079
     *
12080
     * @return learnpathItem|false
12081
     */
12082
    public function getItem($itemId)
12083
    {
12084
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
12085
            return $this->items[$itemId];
12086
        }
12087
12088
        return false;
12089
    }
12090
12091
    /**
12092
     * @return int
12093
     */
12094
    public function getCurrentAttempt()
12095
    {
12096
        $attempt = $this->getItem($this->get_current_item_id());
12097
        if ($attempt) {
12098
            $attemptId = $attempt->get_attempt_id();
12099
12100
            return $attemptId;
12101
        }
12102
12103
        return 0;
12104
    }
12105
12106
    /**
12107
     * @return int
12108
     */
12109
    public function getCategoryId()
12110
    {
12111
        return (int) $this->categoryId;
12112
    }
12113
12114
    /**
12115
     * @param int $categoryId
12116
     *
12117
     * @return bool
12118
     */
12119
    public function setCategoryId($categoryId)
12120
    {
12121
        $this->categoryId = (int) $categoryId;
12122
        $table = Database::get_course_table(TABLE_LP_MAIN);
12123
        $lp_id = $this->get_id();
12124
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
12125
                WHERE iid = $lp_id";
12126
        Database::query($sql);
12127
12128
        return true;
12129
    }
12130
12131
    /**
12132
     * Get whether this is a learning path with the possibility to subscribe
12133
     * users or not.
12134
     *
12135
     * @return int
12136
     */
12137
    public function getSubscribeUsers()
12138
    {
12139
        return $this->subscribeUsers;
12140
    }
12141
12142
    /**
12143
     * Set whether this is a learning path with the possibility to subscribe
12144
     * users or not.
12145
     *
12146
     * @param int $value (0 = false, 1 = true)
12147
     *
12148
     * @return bool
12149
     */
12150
    public function setSubscribeUsers($value)
12151
    {
12152
        $this->subscribeUsers = (int) $value;
12153
        $table = Database::get_course_table(TABLE_LP_MAIN);
12154
        $lp_id = $this->get_id();
12155
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
12156
                WHERE iid = $lp_id";
12157
        Database::query($sql);
12158
12159
        return true;
12160
    }
12161
12162
    /**
12163
     * Calculate the count of stars for a user in this LP
12164
     * This calculation is based on the following rules:
12165
     * - the student gets one star when he gets to 50% of the learning path
12166
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
12167
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
12168
     * - the student gets the final star when the score for the *last* test is >= 80%.
12169
     *
12170
     * @param int $sessionId Optional. The session ID
12171
     *
12172
     * @return int The count of stars
12173
     */
12174
    public function getCalculateStars($sessionId = 0)
12175
    {
12176
        $stars = 0;
12177
        $progress = self::getProgress(
12178
            $this->lp_id,
12179
            $this->user_id,
12180
            $this->course_int_id,
12181
            $sessionId
12182
        );
12183
12184
        if ($progress >= 50) {
12185
            $stars++;
12186
        }
12187
12188
        // Calculate stars chapters evaluation
12189
        $exercisesItems = $this->getExercisesItems();
12190
12191
        if (!empty($exercisesItems)) {
12192
            $totalResult = 0;
12193
12194
            foreach ($exercisesItems as $exerciseItem) {
12195
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12196
                    $this->user_id,
12197
                    $exerciseItem->path,
12198
                    $this->course_int_id,
12199
                    $sessionId,
12200
                    $this->lp_id,
12201
                    $exerciseItem->db_id
12202
                );
12203
12204
                $exerciseResultInfo = end($exerciseResultInfo);
12205
12206
                if (!$exerciseResultInfo) {
12207
                    continue;
12208
                }
12209
12210
                if (!empty($exerciseResultInfo['max_score'])) {
12211
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
12212
                } else {
12213
                    $exerciseResult = 0;
12214
                }
12215
                $totalResult += $exerciseResult;
12216
            }
12217
12218
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
12219
12220
            if ($totalExerciseAverage >= 50) {
12221
                $stars++;
12222
            }
12223
12224
            if ($totalExerciseAverage >= 80) {
12225
                $stars++;
12226
            }
12227
        }
12228
12229
        // Calculate star for final evaluation
12230
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12231
12232
        if (!empty($finalEvaluationItem)) {
12233
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12234
                $this->user_id,
12235
                $finalEvaluationItem->path,
12236
                $this->course_int_id,
12237
                $sessionId,
12238
                $this->lp_id,
12239
                $finalEvaluationItem->db_id
12240
            );
12241
12242
            $evaluationResultInfo = end($evaluationResultInfo);
12243
12244
            if ($evaluationResultInfo) {
12245
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
12246
12247
                if ($evaluationResult >= 80) {
12248
                    $stars++;
12249
                }
12250
            }
12251
        }
12252
12253
        return $stars;
12254
    }
12255
12256
    /**
12257
     * Get the items of exercise type.
12258
     *
12259
     * @return array The items. Otherwise return false
12260
     */
12261
    public function getExercisesItems()
12262
    {
12263
        $exercises = [];
12264
        foreach ($this->items as $item) {
12265
            if ($item->type != 'quiz') {
12266
                continue;
12267
            }
12268
            $exercises[] = $item;
12269
        }
12270
12271
        array_pop($exercises);
12272
12273
        return $exercises;
12274
    }
12275
12276
    /**
12277
     * Get the item of exercise type (evaluation type).
12278
     *
12279
     * @return array The final evaluation. Otherwise return false
12280
     */
12281
    public function getFinalEvaluationItem()
12282
    {
12283
        $exercises = [];
12284
        foreach ($this->items as $item) {
12285
            if ($item->type != 'quiz') {
12286
                continue;
12287
            }
12288
12289
            $exercises[] = $item;
12290
        }
12291
12292
        return array_pop($exercises);
12293
    }
12294
12295
    /**
12296
     * Calculate the total points achieved for the current user in this learning path.
12297
     *
12298
     * @param int $sessionId Optional. The session Id
12299
     *
12300
     * @return int
12301
     */
12302
    public function getCalculateScore($sessionId = 0)
12303
    {
12304
        // Calculate stars chapters evaluation
12305
        $exercisesItems = $this->getExercisesItems();
12306
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12307
        $totalExercisesResult = 0;
12308
        $totalEvaluationResult = 0;
12309
12310
        if ($exercisesItems !== false) {
12311
            foreach ($exercisesItems as $exerciseItem) {
12312
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12313
                    $this->user_id,
12314
                    $exerciseItem->path,
12315
                    $this->course_int_id,
12316
                    $sessionId,
12317
                    $this->lp_id,
12318
                    $exerciseItem->db_id
12319
                );
12320
12321
                $exerciseResultInfo = end($exerciseResultInfo);
12322
12323
                if (!$exerciseResultInfo) {
12324
                    continue;
12325
                }
12326
12327
                $totalExercisesResult += $exerciseResultInfo['score'];
12328
            }
12329
        }
12330
12331
        if (!empty($finalEvaluationItem)) {
12332
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12333
                $this->user_id,
12334
                $finalEvaluationItem->path,
12335
                $this->course_int_id,
12336
                $sessionId,
12337
                $this->lp_id,
12338
                $finalEvaluationItem->db_id
12339
            );
12340
12341
            $evaluationResultInfo = end($evaluationResultInfo);
12342
12343
            if ($evaluationResultInfo) {
12344
                $totalEvaluationResult += $evaluationResultInfo['score'];
12345
            }
12346
        }
12347
12348
        return $totalExercisesResult + $totalEvaluationResult;
12349
    }
12350
12351
    /**
12352
     * Check if URL is not allowed to be show in a iframe.
12353
     *
12354
     * @param string $src
12355
     *
12356
     * @return string
12357
     */
12358
    public function fixBlockedLinks($src)
12359
    {
12360
        $urlInfo = parse_url($src);
12361
12362
        $platformProtocol = 'https';
12363
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
12364
            $platformProtocol = 'http';
12365
        }
12366
12367
        $protocolFixApplied = false;
12368
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
12369
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
12370
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
12371
12372
        if ($platformProtocol != $scheme) {
12373
            Session::write('x_frame_source', $src);
12374
            $src = 'blank.php?error=x_frames_options';
12375
            $protocolFixApplied = true;
12376
        }
12377
12378
        if ($protocolFixApplied == false) {
12379
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
12380
                // Check X-Frame-Options
12381
                $ch = curl_init();
12382
                $options = [
12383
                    CURLOPT_URL => $src,
12384
                    CURLOPT_RETURNTRANSFER => true,
12385
                    CURLOPT_HEADER => true,
12386
                    CURLOPT_FOLLOWLOCATION => true,
12387
                    CURLOPT_ENCODING => "",
12388
                    CURLOPT_AUTOREFERER => true,
12389
                    CURLOPT_CONNECTTIMEOUT => 120,
12390
                    CURLOPT_TIMEOUT => 120,
12391
                    CURLOPT_MAXREDIRS => 10,
12392
                ];
12393
12394
                $proxySettings = api_get_configuration_value('proxy_settings');
12395
                if (!empty($proxySettings) &&
12396
                    isset($proxySettings['curl_setopt_array'])
12397
                ) {
12398
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
12399
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
12400
                }
12401
12402
                curl_setopt_array($ch, $options);
12403
                $response = curl_exec($ch);
12404
                $httpCode = curl_getinfo($ch);
12405
                $headers = substr($response, 0, $httpCode['header_size']);
12406
12407
                $error = false;
12408
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
12409
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
12410
                ) {
12411
                    $error = true;
12412
                }
12413
12414
                if ($error) {
12415
                    Session::write('x_frame_source', $src);
12416
                    $src = 'blank.php?error=x_frames_options';
12417
                }
12418
            }
12419
        }
12420
12421
        return $src;
12422
    }
12423
12424
    /**
12425
     * Check if this LP has a created forum in the basis course.
12426
     *
12427
     * @return bool
12428
     */
12429
    public function lpHasForum()
12430
    {
12431
        $forumTable = Database::get_course_table(TABLE_FORUM);
12432
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12433
12434
        $fakeFrom = "
12435
            $forumTable f
12436
            INNER JOIN $itemProperty ip
12437
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
12438
        ";
12439
12440
        $resultData = Database::select(
12441
            'COUNT(f.iid) AS qty',
12442
            $fakeFrom,
12443
            [
12444
                'where' => [
12445
                    'ip.visibility != ? AND ' => 2,
12446
                    'ip.tool = ? AND ' => TOOL_FORUM,
12447
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12448
                    'f.lp_id = ?' => intval($this->lp_id),
12449
                ],
12450
            ],
12451
            'first'
12452
        );
12453
12454
        return $resultData['qty'] > 0;
12455
    }
12456
12457
    /**
12458
     * Get the forum for this learning path.
12459
     *
12460
     * @param int $sessionId
12461
     *
12462
     * @return bool
12463
     */
12464
    public function getForum($sessionId = 0)
12465
    {
12466
        $forumTable = Database::get_course_table(TABLE_FORUM);
12467
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12468
12469
        $fakeFrom = "$forumTable f
12470
            INNER JOIN $itemProperty ip ";
12471
12472
        if ($this->lp_session_id == 0) {
12473
            $fakeFrom .= "
12474
                ON (
12475
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND (
12476
                        f.session_id = ip.session_id OR ip.session_id IS NULL
12477
                    )
12478
                )
12479
            ";
12480
        } else {
12481
            $fakeFrom .= "
12482
                ON (
12483
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND f.session_id = ip.session_id
12484
                )
12485
            ";
12486
        }
12487
12488
        $resultData = Database::select(
12489
            'f.*',
12490
            $fakeFrom,
12491
            [
12492
                'where' => [
12493
                    'ip.visibility != ? AND ' => 2,
12494
                    'ip.tool = ? AND ' => TOOL_FORUM,
12495
                    'f.session_id = ? AND ' => $sessionId,
12496
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12497
                    'f.lp_id = ?' => intval($this->lp_id),
12498
                ],
12499
            ],
12500
            'first'
12501
        );
12502
12503
        if (empty($resultData)) {
12504
            return false;
12505
        }
12506
12507
        return $resultData;
12508
    }
12509
12510
    /**
12511
     * Create a forum for this learning path.
12512
     *
12513
     * @param int $forumCategoryId
12514
     *
12515
     * @return int The forum ID if was created. Otherwise return false
12516
     */
12517
    public function createForum($forumCategoryId)
12518
    {
12519
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
12520
12521
        $forumId = store_forum(
12522
            [
12523
                'lp_id' => $this->lp_id,
12524
                'forum_title' => $this->name,
12525
                'forum_comment' => null,
12526
                'forum_category' => (int) $forumCategoryId,
12527
                'students_can_edit_group' => ['students_can_edit' => 0],
12528
                'allow_new_threads_group' => ['allow_new_threads' => 0],
12529
                'default_view_type_group' => ['default_view_type' => 'flat'],
12530
                'group_forum' => 0,
12531
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
12532
            ],
12533
            [],
12534
            true
12535
        );
12536
12537
        return $forumId;
12538
    }
12539
12540
    /**
12541
     * Get the LP Final Item form.
12542
     *
12543
     * @throws Exception
12544
     * @throws HTML_QuickForm_Error
12545
     *
12546
     * @return string
12547
     */
12548
    public function getFinalItemForm()
12549
    {
12550
        $finalItem = $this->getFinalItem();
12551
        $title = '';
12552
12553
        if ($finalItem) {
12554
            $title = $finalItem->get_title();
12555
            $buttonText = get_lang('Save');
12556
            $content = $this->getSavedFinalItem();
12557
        } else {
12558
            $buttonText = get_lang('Add this document to the course');
12559
            $content = $this->getFinalItemTemplate();
12560
        }
12561
12562
        $courseInfo = api_get_course_info();
12563
        $result = $this->generate_lp_folder($courseInfo);
12564
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
12565
        $relative_prefix = '../../';
12566
12567
        $editorConfig = [
12568
            'ToolbarSet' => 'LearningPathDocuments',
12569
            'Width' => '100%',
12570
            'Height' => '500',
12571
            'FullPage' => true,
12572
            'CreateDocumentDir' => $relative_prefix,
12573
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
12574
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
12575
        ];
12576
12577
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
12578
            'type' => 'document',
12579
            'lp_id' => $this->lp_id,
12580
        ]);
12581
12582
        $form = new FormValidator('final_item', 'POST', $url);
12583
        $form->addText('title', get_lang('Title'));
12584
        $form->addButtonSave($buttonText);
12585
        $form->addHtml(
12586
            Display::return_message(
12587
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
12588
                'normal',
12589
                false
12590
            )
12591
        );
12592
12593
        $renderer = $form->defaultRenderer();
12594
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
12595
12596
        $form->addHtmlEditor(
12597
            'content_lp_certificate',
12598
            null,
12599
            true,
12600
            false,
12601
            $editorConfig,
12602
            true
12603
        );
12604
        $form->addHidden('action', 'add_final_item');
12605
        $form->addHidden('path', Session::read('pathItem'));
12606
        $form->addHidden('previous', $this->get_last());
12607
        $form->setDefaults(
12608
            ['title' => $title, 'content_lp_certificate' => $content]
12609
        );
12610
12611
        if ($form->validate()) {
12612
            $values = $form->exportValues();
12613
            $lastItemId = $this->getLastInFirstLevel();
12614
12615
            if (!$finalItem) {
12616
                $documentId = $this->create_document(
12617
                    $this->course_info,
12618
                    $values['content_lp_certificate'],
12619
                    $values['title']
12620
                );
12621
                $this->add_item(
12622
                    0,
12623
                    $lastItemId,
12624
                    'final_item',
12625
                    $documentId,
12626
                    $values['title'],
12627
                    ''
12628
                );
12629
12630
                Display::addFlash(
12631
                    Display::return_message(get_lang('Added'))
12632
                );
12633
            } else {
12634
                $this->edit_document($this->course_info);
12635
            }
12636
        }
12637
12638
        return $form->returnForm();
12639
    }
12640
12641
    /**
12642
     * Check if the current lp item is first, both, last or none from lp list.
12643
     *
12644
     * @param int $currentItemId
12645
     *
12646
     * @return string
12647
     */
12648
    public function isFirstOrLastItem($currentItemId)
12649
    {
12650
        $lpItemId = [];
12651
        $typeListNotToVerify = self::getChapterTypes();
12652
12653
        // Using get_toc() function instead $this->items because returns the correct order of the items
12654
        foreach ($this->get_toc() as $item) {
12655
            if (!in_array($item['type'], $typeListNotToVerify)) {
12656
                $lpItemId[] = $item['id'];
12657
            }
12658
        }
12659
12660
        $lastLpItemIndex = count($lpItemId) - 1;
12661
        $position = array_search($currentItemId, $lpItemId);
12662
12663
        switch ($position) {
12664
            case 0:
12665
                if (!$lastLpItemIndex) {
12666
                    $answer = 'both';
12667
                    break;
12668
                }
12669
12670
                $answer = 'first';
12671
                break;
12672
            case $lastLpItemIndex:
12673
                $answer = 'last';
12674
                break;
12675
            default:
12676
                $answer = 'none';
12677
        }
12678
12679
        return $answer;
12680
    }
12681
12682
    /**
12683
     * Get whether this is a learning path with the accumulated SCORM time or not.
12684
     *
12685
     * @return int
12686
     */
12687
    public function getAccumulateScormTime()
12688
    {
12689
        return $this->accumulateScormTime;
12690
    }
12691
12692
    /**
12693
     * Set whether this is a learning path with the accumulated SCORM time or not.
12694
     *
12695
     * @param int $value (0 = false, 1 = true)
12696
     *
12697
     * @return bool Always returns true
12698
     */
12699
    public function setAccumulateScormTime($value)
12700
    {
12701
        $this->accumulateScormTime = (int) $value;
12702
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
12703
        $lp_id = $this->get_id();
12704
        $sql = "UPDATE $lp_table
12705
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
12706
                WHERE iid = $lp_id";
12707
        Database::query($sql);
12708
12709
        return true;
12710
    }
12711
12712
    /**
12713
     * Returns an HTML-formatted link to a resource, to incorporate directly into
12714
     * the new learning path tool.
12715
     *
12716
     * The function is a big switch on tool type.
12717
     * In each case, we query the corresponding table for information and build the link
12718
     * with that information.
12719
     *
12720
     * @author Yannick Warnier <[email protected]> - rebranding based on
12721
     * previous work (display_addedresource_link_in_learnpath())
12722
     *
12723
     * @param int $course_id      Course code
12724
     * @param int $learningPathId The learning path ID (in lp table)
12725
     * @param int $id_in_path     the unique index in the items table
12726
     * @param int $lpViewId
12727
     *
12728
     * @return string
12729
     */
12730
    public static function rl_get_resource_link_for_learnpath(
12731
        $course_id,
12732
        $learningPathId,
12733
        $id_in_path,
12734
        $lpViewId
12735
    ) {
12736
        $session_id = api_get_session_id();
12737
        $course_info = api_get_course_info_by_id($course_id);
12738
12739
        $learningPathId = (int) $learningPathId;
12740
        $id_in_path = (int) $id_in_path;
12741
        $lpViewId = (int) $lpViewId;
12742
12743
        $em = Database::getManager();
12744
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
12745
12746
        /** @var CLpItem $rowItem */
12747
        $rowItem = $lpItemRepo->findOneBy([
12748
            'cId' => $course_id,
12749
            'lpId' => $learningPathId,
12750
            'iid' => $id_in_path,
12751
        ]);
12752
12753
        if (!$rowItem) {
12754
            // Try one more time with "id"
12755
            /** @var CLpItem $rowItem */
12756
            $rowItem = $lpItemRepo->findOneBy([
12757
                'cId' => $course_id,
12758
                'lpId' => $learningPathId,
12759
                'id' => $id_in_path,
12760
            ]);
12761
12762
            if (!$rowItem) {
12763
                return -1;
12764
            }
12765
        }
12766
12767
        $type = $rowItem->getItemType();
12768
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
12769
        $main_dir_path = api_get_path(WEB_CODE_PATH);
12770
        //$main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
12771
        $link = '';
12772
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
12773
12774
        switch ($type) {
12775
            case 'dir':
12776
                return $main_dir_path.'lp/blank.php';
12777
            case TOOL_CALENDAR_EVENT:
12778
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
12779
            case TOOL_ANNOUNCEMENT:
12780
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
12781
            case TOOL_LINK:
12782
                $linkInfo = Link::getLinkInfo($id);
12783
                if (isset($linkInfo['url'])) {
12784
                    return $linkInfo['url'];
12785
                }
12786
12787
                return '';
12788
            case TOOL_QUIZ:
12789
                if (empty($id)) {
12790
                    return '';
12791
                }
12792
12793
                // Get the lp_item_view with the highest view_count.
12794
                $learnpathItemViewResult = $em
12795
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
12796
                    ->findBy(
12797
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
12798
                        ['viewCount' => 'DESC'],
12799
                        1
12800
                    );
12801
                /** @var CLpItemView $learnpathItemViewData */
12802
                $learnpathItemViewData = current($learnpathItemViewResult);
12803
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
12804
12805
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
12806
                    .http_build_query([
12807
                        'lp_init' => 1,
12808
                        'learnpath_item_view_id' => $learnpathItemViewId,
12809
                        'learnpath_id' => $learningPathId,
12810
                        'learnpath_item_id' => $id_in_path,
12811
                        'exerciseId' => $id,
12812
                    ]);
12813
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
12814
                /*$TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
12815
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
12816
                $myrow = Database::fetch_array($result);
12817
                $path = $myrow['path'];
12818
12819
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
12820
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
12821
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;*/
12822
            case TOOL_FORUM:
12823
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
12824
            case TOOL_THREAD:
12825
                // forum post
12826
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
12827
                if (empty($id)) {
12828
                    return '';
12829
                }
12830
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
12831
                $result = Database::query($sql);
12832
                $myrow = Database::fetch_array($result);
12833
12834
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
12835
                    .$extraParams;
12836
            case TOOL_POST:
12837
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12838
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
12839
                $myrow = Database::fetch_array($result);
12840
12841
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
12842
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
12843
            case TOOL_READOUT_TEXT:
12844
                return api_get_path(WEB_CODE_PATH).
12845
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
12846
            case TOOL_DOCUMENT:
12847
                $repo = Container::getDocumentRepository();
12848
                $document = $repo->find($rowItem->getPath());
12849
                $file = $repo->getResourceFileUrl($document, [], UrlGeneratorInterface::ABSOLUTE_URL);
12850
12851
                return $file;
12852
12853
                $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...
12854
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v'];
12855
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
12856
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
12857
12858
                $openmethod = 2;
12859
                $officedoc = false;
12860
                Session::write('openmethod', $openmethod);
12861
                Session::write('officedoc', $officedoc);
12862
12863
                if ($showDirectUrl) {
12864
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
12865
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
12866
                        if (Link::isPdfLink($file)) {
12867
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
12868
12869
                            return $pdfUrl;
12870
                        }
12871
                    }
12872
12873
                    return $file;
12874
                }
12875
12876
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
12877
            case TOOL_LP_FINAL_ITEM:
12878
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
12879
                    .$extraParams;
12880
            case 'assignments':
12881
                return $main_dir_path.'work/work.php?'.$extraParams;
12882
            case TOOL_DROPBOX:
12883
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
12884
            case 'introduction_text': //DEPRECATED
12885
                return '';
12886
            case TOOL_COURSE_DESCRIPTION:
12887
                return $main_dir_path.'course_description?'.$extraParams;
12888
            case TOOL_GROUP:
12889
                return $main_dir_path.'group/group.php?'.$extraParams;
12890
            case TOOL_USER:
12891
                return $main_dir_path.'user/user.php?'.$extraParams;
12892
            case TOOL_STUDENTPUBLICATION:
12893
                if (!empty($rowItem->getPath())) {
12894
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
12895
                }
12896
12897
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
12898
        }
12899
12900
        return $link;
12901
    }
12902
12903
    /**
12904
     * Gets the name of a resource (generally used in learnpath when no name is provided).
12905
     *
12906
     * @author Yannick Warnier <[email protected]>
12907
     *
12908
     * @param string $course_code    Course code
12909
     * @param int    $learningPathId
12910
     * @param int    $id_in_path     The resource ID
12911
     *
12912
     * @return string
12913
     */
12914
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
12915
    {
12916
        $_course = api_get_course_info($course_code);
12917
        if (empty($_course)) {
12918
            return '';
12919
        }
12920
        $course_id = $_course['real_id'];
12921
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12922
        $learningPathId = (int) $learningPathId;
12923
        $id_in_path = (int) $id_in_path;
12924
12925
        $sql = "SELECT item_type, title, ref
12926
                FROM $tbl_lp_item
12927
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
12928
        $res_item = Database::query($sql);
12929
12930
        if (Database::num_rows($res_item) < 1) {
12931
            return '';
12932
        }
12933
        $row_item = Database::fetch_array($res_item);
12934
        $type = strtolower($row_item['item_type']);
12935
        $id = $row_item['ref'];
12936
        $output = '';
12937
12938
        switch ($type) {
12939
            case TOOL_CALENDAR_EVENT:
12940
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
12941
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
12942
                $myrow = Database::fetch_array($result);
12943
                $output = $myrow['title'];
12944
                break;
12945
            case TOOL_ANNOUNCEMENT:
12946
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
12947
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
12948
                $myrow = Database::fetch_array($result);
12949
                $output = $myrow['title'];
12950
                break;
12951
            case TOOL_LINK:
12952
                // Doesn't take $target into account.
12953
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
12954
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
12955
                $myrow = Database::fetch_array($result);
12956
                $output = $myrow['title'];
12957
                break;
12958
            case TOOL_QUIZ:
12959
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
12960
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
12961
                $myrow = Database::fetch_array($result);
12962
                $output = $myrow['title'];
12963
                break;
12964
            case TOOL_FORUM:
12965
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
12966
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
12967
                $myrow = Database::fetch_array($result);
12968
                $output = $myrow['forum_name'];
12969
                break;
12970
            case TOOL_THREAD:
12971
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12972
                // Grabbing the title of the post.
12973
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
12974
                $result_title = Database::query($sql_title);
12975
                $myrow_title = Database::fetch_array($result_title);
12976
                $output = $myrow_title['post_title'];
12977
                break;
12978
            case TOOL_POST:
12979
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12980
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
12981
                $result = Database::query($sql);
12982
                $post = Database::fetch_array($result);
12983
                $output = $post['post_title'];
12984
                break;
12985
            case 'dir':
12986
            case TOOL_DOCUMENT:
12987
                $title = $row_item['title'];
12988
                $output = '-';
12989
                if (!empty($title)) {
12990
                    $output = $title;
12991
                }
12992
                break;
12993
            case 'hotpotatoes':
12994
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
12995
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
12996
                $myrow = Database::fetch_array($result);
12997
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
12998
                $last = count($pathname) - 1; // Making a correct name for the link.
12999
                $filename = $pathname[$last]; // Making a correct name for the link.
13000
                $myrow['path'] = rawurlencode($myrow['path']);
13001
                $output = $filename;
13002
                break;
13003
        }
13004
13005
        return stripslashes($output);
13006
    }
13007
13008
    /**
13009
     * Get the parent names for the current item.
13010
     *
13011
     * @param int $newItemId Optional. The item ID
13012
     *
13013
     * @return array
13014
     */
13015
    public function getCurrentItemParentNames($newItemId = 0)
13016
    {
13017
        $newItemId = $newItemId ?: $this->get_current_item_id();
13018
        $return = [];
13019
        $item = $this->getItem($newItemId);
13020
        $parent = $this->getItem($item->get_parent());
13021
13022
        while ($parent) {
13023
            $return[] = $parent->get_title();
13024
            $parent = $this->getItem($parent->get_parent());
13025
        }
13026
13027
        return array_reverse($return);
13028
    }
13029
13030
    /**
13031
     * Reads and process "lp_subscription_settings" setting.
13032
     *
13033
     * @return array
13034
     */
13035
    public static function getSubscriptionSettings()
13036
    {
13037
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
13038
        if (empty($subscriptionSettings)) {
13039
            // By default allow both settings
13040
            $subscriptionSettings = [
13041
                'allow_add_users_to_lp' => true,
13042
                'allow_add_users_to_lp_category' => true,
13043
            ];
13044
        } else {
13045
            $subscriptionSettings = $subscriptionSettings['options'];
13046
        }
13047
13048
        return $subscriptionSettings;
13049
    }
13050
13051
    /**
13052
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
13053
     */
13054
    public function exportToCourseBuildFormat()
13055
    {
13056
        if (!api_is_allowed_to_edit()) {
13057
            return false;
13058
        }
13059
13060
        $courseBuilder = new CourseBuilder();
13061
        $itemList = [];
13062
        /** @var learnpathItem $item */
13063
        foreach ($this->items as $item) {
13064
            $itemList[$item->get_type()][] = $item->get_path();
13065
        }
13066
13067
        if (empty($itemList)) {
13068
            return false;
13069
        }
13070
13071
        if (isset($itemList['document'])) {
13072
            // Get parents
13073
            foreach ($itemList['document'] as $documentId) {
13074
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
13075
                if (!empty($documentInfo['parents'])) {
13076
                    foreach ($documentInfo['parents'] as $parentInfo) {
13077
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
13078
                            continue;
13079
                        }
13080
                        $itemList['document'][] = $parentInfo['iid'];
13081
                    }
13082
                }
13083
            }
13084
13085
            $courseInfo = api_get_course_info();
13086
            foreach ($itemList['document'] as $documentId) {
13087
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
13088
                $items = DocumentManager::get_resources_from_source_html(
13089
                    $documentInfo['absolute_path'],
13090
                    true,
13091
                    TOOL_DOCUMENT
13092
                );
13093
13094
                if (!empty($items)) {
13095
                    foreach ($items as $item) {
13096
                        // Get information about source url
13097
                        $url = $item[0]; // url
13098
                        $scope = $item[1]; // scope (local, remote)
13099
                        $type = $item[2]; // type (rel, abs, url)
13100
13101
                        $origParseUrl = parse_url($url);
13102
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
13103
13104
                        if ($scope == 'local') {
13105
                            if ($type == 'abs' || $type == 'rel') {
13106
                                $documentFile = strstr($realOrigPath, 'document');
13107
                                if (strpos($realOrigPath, $documentFile) !== false) {
13108
                                    $documentFile = str_replace('document', '', $documentFile);
13109
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
13110
                                    // Document found! Add it to the list
13111
                                    if ($itemDocumentId) {
13112
                                        $itemList['document'][] = $itemDocumentId;
13113
                                    }
13114
                                }
13115
                            }
13116
                        }
13117
                    }
13118
                }
13119
            }
13120
13121
            $courseBuilder->build_documents(
13122
                api_get_session_id(),
13123
                $this->get_course_int_id(),
13124
                true,
13125
                $itemList['document']
13126
            );
13127
        }
13128
13129
        if (isset($itemList['quiz'])) {
13130
            $courseBuilder->build_quizzes(
13131
                api_get_session_id(),
13132
                $this->get_course_int_id(),
13133
                true,
13134
                $itemList['quiz']
13135
            );
13136
        }
13137
13138
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
13139
13140
        /*if (!empty($itemList['thread'])) {
13141
            $postList = [];
13142
            foreach ($itemList['thread'] as $postId) {
13143
                $post = get_post_information($postId);
13144
                if ($post) {
13145
                    if (!isset($itemList['forum'])) {
13146
                        $itemList['forum'] = [];
13147
                    }
13148
                    $itemList['forum'][] = $post['forum_id'];
13149
                    $postList[] = $postId;
13150
                }
13151
            }
13152
13153
            if (!empty($postList)) {
13154
                $courseBuilder->build_forum_posts(
13155
                    $this->get_course_int_id(),
13156
                    null,
13157
                    null,
13158
                    $postList
13159
                );
13160
            }
13161
        }*/
13162
13163
        if (!empty($itemList['thread'])) {
13164
            $threadList = [];
13165
            $em = Database::getManager();
13166
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
13167
            foreach ($itemList['thread'] as $threadId) {
13168
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
13169
                $thread = $repo->find($threadId);
13170
                if ($thread) {
13171
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
13172
                    $threadList[] = $thread->getIid();
13173
                }
13174
            }
13175
13176
            if (!empty($threadList)) {
13177
                $courseBuilder->build_forum_topics(
13178
                    api_get_session_id(),
13179
                    $this->get_course_int_id(),
13180
                    null,
13181
                    $threadList
13182
                );
13183
            }
13184
        }
13185
13186
        $forumCategoryList = [];
13187
        if (isset($itemList['forum'])) {
13188
            foreach ($itemList['forum'] as $forumId) {
13189
                $forumInfo = get_forums($forumId);
13190
                $forumCategoryList[] = $forumInfo['forum_category'];
13191
            }
13192
        }
13193
13194
        if (!empty($forumCategoryList)) {
13195
            $courseBuilder->build_forum_category(
13196
                api_get_session_id(),
13197
                $this->get_course_int_id(),
13198
                true,
13199
                $forumCategoryList
13200
            );
13201
        }
13202
13203
        if (!empty($itemList['forum'])) {
13204
            $courseBuilder->build_forums(
13205
                api_get_session_id(),
13206
                $this->get_course_int_id(),
13207
                true,
13208
                $itemList['forum']
13209
            );
13210
        }
13211
13212
        if (isset($itemList['link'])) {
13213
            $courseBuilder->build_links(
13214
                api_get_session_id(),
13215
                $this->get_course_int_id(),
13216
                true,
13217
                $itemList['link']
13218
            );
13219
        }
13220
13221
        if (!empty($itemList['student_publication'])) {
13222
            $courseBuilder->build_works(
13223
                api_get_session_id(),
13224
                $this->get_course_int_id(),
13225
                true,
13226
                $itemList['student_publication']
13227
            );
13228
        }
13229
13230
        $courseBuilder->build_learnpaths(
13231
            api_get_session_id(),
13232
            $this->get_course_int_id(),
13233
            true,
13234
            [$this->get_id()],
13235
            false
13236
        );
13237
13238
        $courseBuilder->restoreDocumentsFromList();
13239
13240
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
13241
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
13242
        $result = DocumentManager::file_send_for_download(
13243
            $zipPath,
13244
            true,
13245
            $this->get_name().'.zip'
13246
        );
13247
13248
        if ($result) {
13249
            api_not_allowed();
13250
        }
13251
13252
        return true;
13253
    }
13254
13255
    /**
13256
     * Get whether this is a learning path with the accumulated work time or not.
13257
     *
13258
     * @return int
13259
     */
13260
    public function getAccumulateWorkTime()
13261
    {
13262
        return (int) $this->accumulateWorkTime;
13263
    }
13264
13265
    /**
13266
     * Get whether this is a learning path with the accumulated work time or not.
13267
     *
13268
     * @return int
13269
     */
13270
    public function getAccumulateWorkTimeTotalCourse()
13271
    {
13272
        $table = Database::get_course_table(TABLE_LP_MAIN);
13273
        $sql = "SELECT SUM(accumulate_work_time) AS total
13274
                FROM $table
13275
                WHERE c_id = ".$this->course_int_id;
13276
        $result = Database::query($sql);
13277
        $row = Database::fetch_array($result);
13278
13279
        return (int) $row['total'];
13280
    }
13281
13282
    /**
13283
     * Set whether this is a learning path with the accumulated work time or not.
13284
     *
13285
     * @param int $value (0 = false, 1 = true)
13286
     *
13287
     * @return bool
13288
     */
13289
    public function setAccumulateWorkTime($value)
13290
    {
13291
        if (!api_get_configuration_value('lp_minimum_time')) {
13292
            return false;
13293
        }
13294
13295
        $this->accumulateWorkTime = (int) $value;
13296
        $table = Database::get_course_table(TABLE_LP_MAIN);
13297
        $lp_id = $this->get_id();
13298
        $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
13299
                WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
13300
        Database::query($sql);
13301
13302
        return true;
13303
    }
13304
13305
    /**
13306
     * @param int $lpId
13307
     * @param int $courseId
13308
     *
13309
     * @return mixed
13310
     */
13311
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
13312
    {
13313
        $lpId = (int) $lpId;
13314
        $courseId = (int) $courseId;
13315
13316
        $table = Database::get_course_table(TABLE_LP_MAIN);
13317
        $sql = "SELECT accumulate_work_time
13318
                FROM $table
13319
                WHERE c_id = $courseId AND id = $lpId";
13320
        $result = Database::query($sql);
13321
        $row = Database::fetch_array($result);
13322
13323
        return $row['accumulate_work_time'];
13324
    }
13325
13326
    /**
13327
     * @param int $courseId
13328
     *
13329
     * @return int
13330
     */
13331
    public static function getAccumulateWorkTimeTotal($courseId)
13332
    {
13333
        $table = Database::get_course_table(TABLE_LP_MAIN);
13334
        $courseId = (int) $courseId;
13335
        $sql = "SELECT SUM(accumulate_work_time) AS total
13336
                FROM $table
13337
                WHERE c_id = $courseId";
13338
        $result = Database::query($sql);
13339
        $row = Database::fetch_array($result);
13340
13341
        return (int) $row['total'];
13342
    }
13343
13344
    /**
13345
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
13346
     * and put the images in.
13347
     *
13348
     * @return array
13349
     */
13350
    public static function getIconSelect()
13351
    {
13352
        $theme = api_get_visual_theme();
13353
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
13354
        $icons = ['' => get_lang('Please select an option')];
13355
13356
        if (is_dir($path)) {
13357
            $finder = new Finder();
13358
            $finder->files()->in($path);
13359
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
13360
            /** @var SplFileInfo $file */
13361
            foreach ($finder as $file) {
13362
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
13363
                    $icons[$file->getFilename()] = $file->getFilename();
13364
                }
13365
            }
13366
        }
13367
13368
        return $icons;
13369
    }
13370
13371
    /**
13372
     * @param int $lpId
13373
     *
13374
     * @return string
13375
     */
13376
    public static function getSelectedIcon($lpId)
13377
    {
13378
        $extraFieldValue = new ExtraFieldValue('lp');
13379
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
13380
        $icon = '';
13381
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
13382
            $icon = $lpIcon['value'];
13383
        }
13384
13385
        return $icon;
13386
    }
13387
13388
    /**
13389
     * @param int $lpId
13390
     *
13391
     * @return string
13392
     */
13393
    public static function getSelectedIconHtml($lpId)
13394
    {
13395
        $icon = self::getSelectedIcon($lpId);
13396
13397
        if (empty($icon)) {
13398
            return '';
13399
        }
13400
13401
        $theme = api_get_visual_theme();
13402
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
13403
13404
        return Display::img($path);
13405
    }
13406
13407
    /**
13408
     * @param string $value
13409
     *
13410
     * @return string
13411
     */
13412
    public function cleanItemTitle($value)
13413
    {
13414
        $value = Security::remove_XSS(strip_tags($value));
13415
13416
        return $value;
13417
    }
13418
13419
    public function setItemTitle(FormValidator $form)
13420
    {
13421
        if (api_get_configuration_value('save_titles_as_html')) {
13422
            $form->addHtmlEditor(
13423
                'title',
13424
                get_lang('Title'),
13425
                true,
13426
                false,
13427
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
13428
            );
13429
        } else {
13430
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
13431
            $form->applyFilter('title', 'trim');
13432
            $form->applyFilter('title', 'html_filter');
13433
        }
13434
    }
13435
13436
    /**
13437
     * @return array
13438
     */
13439
    public function getItemsForForm($addParentCondition = false)
13440
    {
13441
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
13442
        $course_id = api_get_course_int_id();
13443
13444
        $sql = "SELECT * FROM $tbl_lp_item
13445
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
13446
13447
        if ($addParentCondition) {
13448
            $sql .= ' AND parent_item_id = 0 ';
13449
        }
13450
        $sql .= ' ORDER BY display_order ASC';
13451
13452
        $result = Database::query($sql);
13453
        $arrLP = [];
13454
        while ($row = Database::fetch_array($result)) {
13455
            $arrLP[] = [
13456
                'iid' => $row['iid'],
13457
                'id' => $row['iid'],
13458
                'item_type' => $row['item_type'],
13459
                'title' => $this->cleanItemTitle($row['title']),
13460
                'title_raw' => $row['title'],
13461
                'path' => $row['path'],
13462
                'description' => Security::remove_XSS($row['description']),
13463
                'parent_item_id' => $row['parent_item_id'],
13464
                'previous_item_id' => $row['previous_item_id'],
13465
                'next_item_id' => $row['next_item_id'],
13466
                'display_order' => $row['display_order'],
13467
                'max_score' => $row['max_score'],
13468
                'min_score' => $row['min_score'],
13469
                'mastery_score' => $row['mastery_score'],
13470
                'prerequisite' => $row['prerequisite'],
13471
                'max_time_allowed' => $row['max_time_allowed'],
13472
                'prerequisite_min_score' => $row['prerequisite_min_score'],
13473
                'prerequisite_max_score' => $row['prerequisite_max_score'],
13474
            ];
13475
        }
13476
13477
        return $arrLP;
13478
    }
13479
13480
    /**
13481
     * Get the depth level of LP item.
13482
     *
13483
     * @param array $items
13484
     * @param int   $currentItemId
13485
     *
13486
     * @return int
13487
     */
13488
    private static function get_level_for_item($items, $currentItemId)
13489
    {
13490
        $parentItemId = 0;
13491
        if (isset($items[$currentItemId])) {
13492
            $parentItemId = $items[$currentItemId]->parent;
13493
        }
13494
13495
        if ($parentItemId == 0) {
13496
            return 0;
13497
        } else {
13498
            return self::get_level_for_item($items, $parentItemId) + 1;
13499
        }
13500
    }
13501
13502
    /**
13503
     * Generate the link for a learnpath category as course tool.
13504
     *
13505
     * @param int $categoryId
13506
     *
13507
     * @return string
13508
     */
13509
    private static function getCategoryLinkForTool($categoryId)
13510
    {
13511
        $categoryId = (int) $categoryId;
13512
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
13513
            .http_build_query(
13514
                [
13515
                    'action' => 'view_category',
13516
                    'id' => $categoryId,
13517
                ]
13518
            );
13519
13520
        return $link;
13521
    }
13522
13523
    /**
13524
     * Return the scorm item type object with spaces replaced with _
13525
     * The return result is use to build a css classname like scorm_type_$return.
13526
     *
13527
     * @param $in_type
13528
     *
13529
     * @return mixed
13530
     */
13531
    private static function format_scorm_type_item($in_type)
13532
    {
13533
        return str_replace(' ', '_', $in_type);
13534
    }
13535
13536
    /**
13537
     * Check and obtain the lp final item if exist.
13538
     *
13539
     * @return learnpathItem
13540
     */
13541
    private function getFinalItem()
13542
    {
13543
        if (empty($this->items)) {
13544
            return null;
13545
        }
13546
13547
        foreach ($this->items as $item) {
13548
            if ($item->type !== 'final_item') {
13549
                continue;
13550
            }
13551
13552
            return $item;
13553
        }
13554
    }
13555
13556
    /**
13557
     * Get the LP Final Item Template.
13558
     *
13559
     * @return string
13560
     */
13561
    private function getFinalItemTemplate()
13562
    {
13563
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
13564
    }
13565
13566
    /**
13567
     * Get the LP Final Item Url.
13568
     *
13569
     * @return string
13570
     */
13571
    private function getSavedFinalItem()
13572
    {
13573
        $finalItem = $this->getFinalItem();
13574
        $doc = DocumentManager::get_document_data_by_id(
13575
            $finalItem->path,
13576
            $this->cc
13577
        );
13578
        if ($doc && file_exists($doc['absolute_path'])) {
13579
            return file_get_contents($doc['absolute_path']);
13580
        }
13581
13582
        return '';
13583
    }
13584
}
13585