Passed
Push — master ( cf2309...45bdfa )
by Julito
13:01
created

learnpath   F

Complexity

Total Complexity 1760

Size/Duplication

Total Lines 13347
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 7157
c 5
b 0
f 0
dl 0
loc 13347
rs 0.8
wmc 1760

219 Methods

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

How to fix   Complexity   

Complex Class

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

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

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

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

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

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

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