Passed
Push — master ( 39ba65...4e7d2f )
by Julito
10:09
created

learnpath   F

Complexity

Total Complexity 1426

Size/Duplication

Total Lines 11480
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 5996
c 0
b 0
f 0
dl 0
loc 11480
rs 0.8
wmc 1426

208 Methods

Rating   Name   Duplication   Size   Complexity  
A get_previous_index() 0 16 5
A get_previous_item_id() 0 5 1
F edit_item() 0 212 22
B delete_item() 0 70 6
A delete_children_items() 0 22 4
A get_total_items_count() 0 3 1
A get_first_item_id() 0 8 2
A get_last() 0 10 2
A get_js_lib() 0 8 2
F first() 0 71 20
B set_terms_by_prefix() 0 68 10
A get_current_item_id() 0 8 2
A getLastInFirstLevel() 0 13 2
A get_id() 0 7 2
A get_complete_items_count() 0 24 5
A getTotalItemsCountWithoutDirs() 0 11 3
A get_common_index_terms_by_prefix() 0 17 3
A set_jslib() 0 15 2
B set_current_item() 0 33 10
A set_encoding() 0 19 4
A getProgressBar() 0 5 1
B update_default_view_mode() 0 33 6
A next() 0 22 5
B get_iv_objectives_array() 0 38 6
A togglePublish() 0 22 4
A getCategory() 0 6 1
D display_edit_item() 0 84 20
A set_autolaunch() 0 27 2
A display_thread_form() 0 13 1
B getCalculateScore() 0 47 6
F save_last() 0 80 21
A get_update_queue() 0 3 1
A copy() 0 26 1
A displayDocumentForm() 0 46 4
A getCategoryFromCourseIntoSelect() 0 15 4
F get_link() 0 308 51
A switch_attempt_mode() 0 16 4
A getAccumulateScormTime() 0 3 1
F create_document() 0 146 26
A get_course_int_id() 0 3 2
D prerequisites_match() 0 69 16
A previous() 0 11 1
A tree_array() 0 4 1
A createCategory() 0 24 1
C get_exercises() 0 131 11
B move_down() 0 54 8
C fixBlockedLinks() 0 64 11
A open() 0 9 1
A createForum() 0 18 1
A update_default_scorm_commit() 0 25 4
A getLpList() 0 9 1
A getCategorySessionId() 0 18 3
B getChildrenToc() 0 49 11
A set_error_msg() 0 9 3
D stop_previous_item() 0 55 18
B set_previous_step_as_prerequisite_for_all_items() 0 48 7
A updateCategory() 0 9 2
C start_current_item() 0 40 16
A displayItemMenu() 0 91 5
A getUserIdentifierForExternalServices() 0 11 3
A getAccumulateWorkTime() 0 3 1
A get_author() 0 7 2
A display_lp_prerequisites_list() 0 34 5
A set_modified_on() 0 10 1
A getHideTableOfContents() 0 3 1
A categoryIsPublished() 0 25 2
A get_type() 0 8 3
A get_maker() 0 7 2
A display_link_form() 0 22 2
A getForum() 0 9 1
A get_progress_bar() 0 12 1
A getLpNameById() 0 8 1
A getExercisesItems() 0 13 3
B getFinalItemForm() 0 86 4
A getSelectedIcon() 0 10 3
B sortItemByOrderList() 0 48 7
A getCountCategories() 0 10 2
A set_course_int_id() 0 3 1
F createReadOutText() 0 121 27
A moveDownCategory() 0 11 2
A get_type_static() 0 16 3
A sort_tree_array() 0 12 3
F scormExport() 0 973 114
A getAccumulateWorkTimePrerequisite() 0 13 1
A getSelectedIconHtml() 0 12 2
A get_items_status_list() 0 10 2
A get_theme() 0 7 2
A display_item_form() 0 14 1
A select_previous_item_id() 0 22 1
F add_item() 0 211 16
A getItemsForForm() 0 39 3
A set_seriousgame_mode() 0 23 4
C isBlockedByPrerequisite() 0 68 13
A returnLpItemList() 0 15 2
B get_progress_bar_text() 0 56 11
A get_teacher_toc_buttons() 0 21 4
B getTOCTree() 0 44 8
A getItem() 0 7 3
A getCategoryId() 0 3 1
B create_tree_array() 0 38 11
B get_links() 0 118 7
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 set_use_max_score() 0 12 1
A create_path() 0 14 5
A set_previous_item() 0 6 2
A getCurrentBuildingModeURL() 0 11 5
A display_document() 0 26 3
A save_current() 0 32 6
A getChapterTypes() 0 4 1
F __construct() 0 267 39
C rl_get_resource_name() 0 92 14
A displayNewSectionForm() 0 17 1
A getSavedFinalItem() 0 13 3
B restart() 0 38 6
A cleanItemTitle() 0 5 1
B get_iv_interactions_array() 0 54 8
A format_scorm_type_item() 0 3 1
A getCurrentItemParentNames() 0 13 3
A getSubscribeUsers() 0 3 1
A set_attempt_mode() 0 32 5
A close() 0 13 2
A deleteCategory() 0 22 3
A update_display_order() 0 29 5
A getNameNoTags() 0 3 1
B generate_lp_folder() 0 52 7
A getAccumulateWorkTimeTotalCourse() 0 10 1
A display_quiz_form() 0 13 1
A moveUpCategory() 0 11 2
C get_mediaplayer() 0 80 13
A getProgressFromLpList() 0 32 4
C display_item() 0 81 14
B overview() 0 50 9
F autocomplete_parents() 0 101 17
B move_up() 0 51 8
A get_interactions_count_from_db() 0 16 2
B get_scorm_prereq_string() 0 73 11
F display_item_prerequisites_form() 0 171 20
B print_recursive() 0 40 10
A clear_prerequisites() 0 14 1
C scorm_export_to_pdf() 0 70 12
B isFirstOrLastItem() 0 32 6
C display_move_item() 0 55 12
C getCalculateStars() 0 80 12
B get_scorm_xml_node() 0 19 7
A has_audio() 0 11 3
B edit_item_prereq() 0 40 7
B get_js_dropdown_array() 0 78 6
A getFinalEvaluationItem() 0 12 3
A setItemTitle() 0 14 2
F build_action_menu() 0 193 15
A get_next_item_id() 0 10 3
B get_view() 0 46 7
A getCategoryByCourse() 0 8 1
F is_lp_visible_for_student() 0 141 25
A get_flat_ordered_items_list() 0 35 5
A getProgress() 0 23 2
A getCategoryLinkForTool() 0 12 1
A getSubscriptionSettings() 0 14 2
B verify_document_size() 0 25 8
A get_objectives_count_from_db() 0 16 2
B get_documents() 0 110 2
B get_forums() 0 112 9
A edit_document() 0 14 4
D move_item() 0 125 18
A getCurrentAttempt() 0 8 2
A display_forum_form() 0 16 2
A update_scorm_debug() 0 23 4
A getUseScoreAsProgress() 0 18 5
A toggleCategoryVisibility() 0 19 3
C getParentToc() 0 54 13
F add_lp() 0 139 15
A getFinalItem() 0 12 4
A displayResources() 0 55 2
A update_reinit() 0 23 4
A getFinalItemTemplate() 0 3 1
D getListArrayToc() 0 67 11
A get_extension() 0 5 1
F rl_get_resource_link_for_learnpath() 0 171 32
A getCourseCode() 0 3 1
A get_view_id() 0 7 2
A getAccumulateWorkTimeTotal() 0 11 1
A getLpFromSession() 0 28 5
C toggleCategoryPublish() 0 103 12
C get_navigation_bar() 0 73 10
A generate_learning_path_folder() 0 24 3
A getCategories() 0 9 1
B get_next_index() 0 23 7
B get_attempt_mode() 0 21 9
D getPackageType() 0 90 20
A toggleVisibility() 0 18 3
A get_lp_session_id() 0 7 2
A return_new_tree() 0 34 4
A getIconSelect() 0 19 4
F exportToCourseBuildFormat() 0 199 31
A get_toc() 0 18 2
F categoryIsVisibleForStudent() 0 89 19
A get_level_for_item() 0 11 3
F delete() 0 125 19
A getEntity() 0 3 1
A get_student_publications() 0 42 3
A get_name() 0 7 2
A lpHasForum() 0 26 1
A display_student_publication_form() 0 15 1
A getStatusCSSClassName() 0 7 2
B save_item() 0 47 9
F processBuildMenuElements() 0 440 52

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\User;
6
use Chamilo\CoreBundle\Framework\Container;
7
use Chamilo\CoreBundle\Repository\Node\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\CForumCategory;
13
use Chamilo\CourseBundle\Entity\CLink;
14
use Chamilo\CourseBundle\Entity\CLp;
15
use Chamilo\CourseBundle\Entity\CLpCategory;
16
use Chamilo\CourseBundle\Entity\CLpItem;
17
use Chamilo\CourseBundle\Entity\CLpItemView;
18
use Chamilo\CourseBundle\Entity\CQuiz;
19
use Chamilo\CourseBundle\Entity\CStudentPublication;
20
use Chamilo\CourseBundle\Entity\CTool;
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
 * @author  Yannick Warnier <[email protected]>
35
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
36
 */
37
class learnpath
38
{
39
    public const MAX_LP_ITEM_TITLE_LENGTH = 32;
40
    public const STATUS_CSS_CLASS_NAME = [
41
        'not attempted' => 'scorm_not_attempted',
42
        'incomplete' => 'scorm_not_attempted',
43
        'failed' => 'scorm_failed',
44
        'completed' => 'scorm_completed',
45
        'passed' => 'scorm_completed',
46
        'succeeded' => 'scorm_completed',
47
        'browsed' => 'scorm_completed',
48
    ];
49
50
    public $attempt = 0; // The number for the current ID view.
51
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
52
    public $current; // Id of the current item the user is viewing.
53
    public $current_score; // The score of the current item.
54
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
55
    public $current_time_stop; // The time the user closed this resource.
56
    public $default_status = 'not attempted';
57
    public $encoding = 'UTF-8';
58
    public $error = '';
59
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
60
    public $index; // The index of the active learnpath_item in $ordered_items array.
61
    /** @var learnpathItem[] */
62
    public $items = [];
63
    public $last; // item_id of last item viewed in the learning path.
64
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
65
    public $license; // Which license this course has been given - not used yet on 20060522.
66
    public $lp_id; // DB iid for this learnpath.
67
    public $lp_view_id; // DB ID for lp_view
68
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
69
    public $message = '';
70
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
71
    public $name; // Learnpath name (they generally have one).
72
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
73
    public $path = ''; // Path inside the scorm directory (if scorm).
74
    public $theme; // The current theme of the learning path.
75
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
76
    public $accumulateWorkTime; // The min time of learnpath
77
78
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
79
    public $prevent_reinit = 1;
80
81
    // Describes the mode of progress bar display.
82
    public $seriousgame_mode = 0;
83
    public $progress_bar_mode = '%';
84
85
    // Percentage progress as saved in the db.
86
    public $progress_db = 0;
87
    public $proximity; // Wether the content is distant or local or unknown.
88
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
89
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
90
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
91
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
92
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
93
    public $user_id; //ID of the user that is viewing/using the course
94
    public $update_queue = [];
95
    public $scorm_debug = 0;
96
    public $arrMenu = []; // Array for the menu items.
97
    public $debug = 0; // Logging level.
98
    public $lp_session_id = 0;
99
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
100
    public $prerequisite = 0;
101
    public $use_max_score = 1; // 1 or 0
102
    public $subscribeUsers = 0; // Subscribe users or not
103
    public $created_on = '';
104
    public $modified_on = '';
105
    public $publicated_on = '';
106
    public $expired_on = '';
107
    public $ref = null;
108
    public $course_int_id;
109
    public $course_info = [];
110
    public $categoryId;
111
    public $entity;
112
113
    /**
114
     * Constructor.
115
     * Needs a database handler, a course code and a learnpath id from the database.
116
     * Also builds the list of items into $this->items.
117
     *
118
     * @param string $course  Course code
119
     * @param int    $lp_id   c_lp.iid
120
     * @param int    $user_id
121
     */
122
    public function __construct($course, $lp_id, $user_id)
123
    {
124
        $debug = $this->debug;
125
        $this->encoding = api_get_system_encoding();
126
        if (empty($course)) {
127
            $course = api_get_course_id();
128
        }
129
        $course_info = api_get_course_info($course);
130
        if (!empty($course_info)) {
131
            $this->cc = $course_info['code'];
132
            $this->course_info = $course_info;
133
            $course_id = $course_info['real_id'];
134
        } else {
135
            $this->error = 'Course code does not exist in database.';
136
        }
137
138
        $lp_id = (int) $lp_id;
139
        $course_id = (int) $course_id;
140
        $this->set_course_int_id($course_id);
141
        // Check learnpath ID.
142
        if (empty($lp_id) || empty($course_id)) {
143
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
144
        } else {
145
            $repo = Container::getLpRepository();
146
            /** @var CLp $entity */
147
            $entity = $repo->find($lp_id);
148
            if ($entity) {
0 ignored issues
show
introduced by
$entity is of type Chamilo\CourseBundle\Entity\CLp, thus it always evaluated to true.
Loading history...
149
                $this->entity = $entity;
150
                $this->lp_id = $lp_id;
151
                $this->type = $entity->getLpType();
152
                $this->name = stripslashes($entity->getName());
153
                $this->proximity = $entity->getContentLocal();
154
                $this->theme = $entity->getTheme();
155
                $this->maker = $entity->getContentLocal();
156
                $this->prevent_reinit = $entity->getPreventReinit();
157
                $this->seriousgame_mode = $entity->getSeriousgameMode();
158
                $this->license = $entity->getContentLicense();
159
                $this->scorm_debug = $entity->getDebug();
160
                $this->js_lib = $entity->getJsLib();
161
                $this->path = $entity->getPath();
162
                $this->author = $entity->getAuthor();
163
                $this->hide_toc_frame = $entity->getHideTocFrame();
164
                $this->lp_session_id = $entity->getSessionId();
165
                $this->use_max_score = $entity->getUseMaxScore();
166
                $this->subscribeUsers = $entity->getSubscribeUsers();
167
                $this->created_on = $entity->getCreatedOn()->format('Y-m-d H:i:s');
168
                $this->modified_on = $entity->getModifiedOn()->format('Y-m-d H:i:s');
169
                $this->ref = $entity->getRef();
170
                $this->categoryId = 0;
171
                if ($entity->getCategory()) {
172
                    $this->categoryId = $entity->getCategory()->getIid();
173
                }
174
175
                $this->accumulateScormTime = $entity->getAccumulateWorkTime();
176
177
                if (!empty($entity->getPublicatedOn())) {
178
                    $this->publicated_on = $entity->getPublicatedOn()->format('Y-m-d H:i:s');
179
                }
180
181
                if (!empty($entity->getExpiredOn())) {
182
                    $this->expired_on = $entity->getExpiredOn()->format('Y-m-d H:i:s');
183
                }
184
                if (2 == $this->type) {
185
                    if (1 == $entity->getForceCommit()) {
186
                        $this->force_commit = true;
187
                    }
188
                }
189
                $this->mode = $entity->getDefaultViewMod();
190
191
                // Check user ID.
192
                if (empty($user_id)) {
193
                    $this->error = 'User ID is empty';
194
                } else {
195
                    $userInfo = api_get_user_info($user_id);
196
                    if (!empty($userInfo)) {
197
                        $this->user_id = $userInfo['user_id'];
198
                    } else {
199
                        $this->error = 'User ID does not exist in database #'.$user_id;
200
                    }
201
                }
202
203
                // End of variables checking.
204
                $session_id = api_get_session_id();
205
                //  Get the session condition for learning paths of the base + session.
206
                $session = api_get_session_condition($session_id);
207
                // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
208
                $lp_table = Database::get_course_table(TABLE_LP_VIEW);
209
210
                // Selecting by view_count descending allows to get the highest view_count first.
211
                $sql = "SELECT * FROM $lp_table
212
                        WHERE
213
                            c_id = $course_id AND
214
                            lp_id = $lp_id AND
215
                            user_id = $user_id
216
                            $session
217
                        ORDER BY view_count DESC";
218
                $res = Database::query($sql);
219
220
                if (Database::num_rows($res) > 0) {
221
                    $row = Database::fetch_array($res);
222
                    $this->attempt = $row['view_count'];
223
                    $this->lp_view_id = $row['iid'];
224
                    $this->last_item_seen = $row['last_item'];
225
                    $this->progress_db = $row['progress'];
226
                    $this->lp_view_session_id = $row['session_id'];
227
                } elseif (!api_is_invitee()) {
228
                    $this->attempt = 1;
229
                    $params = [
230
                        'c_id' => $course_id,
231
                        'lp_id' => $lp_id,
232
                        'user_id' => $user_id,
233
                        'view_count' => 1,
234
                        'session_id' => $session_id,
235
                        'last_item' => 0,
236
                    ];
237
                    $this->last_item_seen = 0;
238
                    $this->lp_view_session_id = $session_id;
239
                    $this->lp_view_id = Database::insert($lp_table, $params);
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
                            }
263
                            break;
264
                        case 2:
265
                            $oItem = new scormItem('db', $row['iid'], $course_id);
266
                            if (is_object($oItem)) {
267
                                $my_item_id = $oItem->get_id();
268
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
269
                                $oItem->set_prevent_reinit($this->prevent_reinit);
270
                                // Don't use reference here as the next loop will make the pointed object change.
271
                                $this->items[$my_item_id] = $oItem;
272
                                $this->refs_list[$oItem->ref] = $my_item_id;
273
                            }
274
                            break;
275
                        case 1:
276
                        default:
277
                            $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
278
                            if (is_object($oItem)) {
279
                                $my_item_id = $oItem->get_id();
280
                                // Moved down to when we are sure the item_view exists.
281
                                //$oItem->set_lp_view($this->lp_view_id);
282
                                $oItem->set_prevent_reinit($this->prevent_reinit);
283
                                // Don't use reference here as the next loop will make the pointed object change.
284
                                $this->items[$my_item_id] = $oItem;
285
                                $this->refs_list[$my_item_id] = $my_item_id;
286
                            }
287
                            break;
288
                    }
289
290
                    // Setting the object level with variable $this->items[$i][parent]
291
                    foreach ($this->items as $itemLPObject) {
292
                        $level = self::get_level_for_item(
293
                            $this->items,
294
                            $itemLPObject->db_id
295
                        );
296
                        $itemLPObject->level = $level;
297
                    }
298
299
                    // Setting the view in the item object.
300
                    if (is_object($this->items[$row['iid']])) {
301
                        $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
302
                        if (TOOL_HOTPOTATOES == $this->items[$row['iid']]->get_type()) {
303
                            $this->items[$row['iid']]->current_start_time = 0;
304
                            $this->items[$row['iid']]->current_stop_time = 0;
305
                        }
306
                    }
307
                }
308
309
                if (!empty($lp_item_id_list)) {
310
                    $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
311
                    if (!empty($lp_item_id_list_to_string)) {
312
                        // Get last viewing vars.
313
                        $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
314
                        // This query should only return one or zero result.
315
                        $sql = "SELECT lp_item_id, status
316
                                FROM $itemViewTable
317
                                WHERE
318
                                    c_id = $course_id AND
319
                                    lp_view_id = ".$this->get_view_id()." AND
320
                                    lp_item_id IN ('".$lp_item_id_list_to_string."')
321
                                ORDER BY view_count DESC ";
322
                        $status_list = [];
323
                        $res = Database::query($sql);
324
                        while ($row = Database:: fetch_array($res)) {
325
                            $status_list[$row['lp_item_id']] = $row['status'];
326
                        }
327
328
                        foreach ($lp_item_id_list as $item_id) {
329
                            if (isset($status_list[$item_id])) {
330
                                $status = $status_list[$item_id];
331
                                if (is_object($this->items[$item_id])) {
332
                                    $this->items[$item_id]->set_status($status);
333
                                    if (empty($status)) {
334
                                        $this->items[$item_id]->set_status(
335
                                            $this->default_status
336
                                        );
337
                                    }
338
                                }
339
                            } else {
340
                                if (!api_is_invitee()) {
341
                                    if (is_object($this->items[$item_id])) {
342
                                        $this->items[$item_id]->set_status(
343
                                            $this->default_status
344
                                        );
345
                                    }
346
347
                                    if (!empty($this->lp_view_id)) {
348
                                        // Add that row to the lp_item_view table so that
349
                                        // we have something to show in the stats page.
350
                                        $params = [
351
                                            'c_id' => $course_id,
352
                                            'lp_item_id' => $item_id,
353
                                            'lp_view_id' => $this->lp_view_id,
354
                                            'view_count' => 1,
355
                                            'status' => 'not attempted',
356
                                            'start_time' => time(),
357
                                            'total_time' => 0,
358
                                            'score' => 0,
359
                                        ];
360
                                        Database::insert($itemViewTable, $params);
361
362
                                        $this->items[$item_id]->set_lp_view(
363
                                            $this->lp_view_id,
364
                                            $course_id
365
                                        );
366
                                    }
367
                                }
368
                            }
369
                        }
370
                    }
371
                }
372
373
                $this->ordered_items = self::get_flat_ordered_items_list(
374
                    $this->get_id(),
375
                    0,
376
                    $course_id
377
                );
378
                $this->max_ordered_items = 0;
379
                foreach ($this->ordered_items as $index => $dummy) {
380
                    if ($index > $this->max_ordered_items && !empty($dummy)) {
381
                        $this->max_ordered_items = $index;
382
                    }
383
                }
384
                // TODO: Define the current item better.
385
                $this->first();
386
                if ($debug) {
387
                    error_log('lp_view_session_id '.$this->lp_view_session_id);
388
                    error_log('End of learnpath constructor for learnpath '.$this->get_id());
389
                }
390
            }
391
        }
392
    }
393
394
    public function getEntity(): CLp
395
    {
396
        return $this->entity;
397
    }
398
399
    /**
400
     * @return string
401
     */
402
    public function getCourseCode()
403
    {
404
        return $this->cc;
405
    }
406
407
    /**
408
     * @return int
409
     */
410
    public function get_course_int_id()
411
    {
412
        return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
413
    }
414
415
    /**
416
     * @param $course_id
417
     *
418
     * @return int
419
     */
420
    public function set_course_int_id($course_id)
421
    {
422
        return $this->course_int_id = (int) $course_id;
423
    }
424
425
    /**
426
     * Function rewritten based on old_add_item() from Yannick Warnier.
427
     * Due the fact that users can decide where the item should come, I had to overlook this function and
428
     * I found it better to rewrite it. Old function is still available.
429
     * Added also the possibility to add a description.
430
     *
431
     * @param int    $parent
432
     * @param int    $previous
433
     * @param string $type
434
     * @param int    $id               resource ID (ref)
435
     * @param string $title
436
     * @param string $description
437
     * @param int    $prerequisites
438
     * @param int    $max_time_allowed
439
     * @param int    $userId
440
     *
441
     * @return int
442
     */
443
    public function add_item(
444
        $parent,
445
        $previous,
446
        $type = 'dir',
447
        $id,
448
        $title,
449
        $description,
450
        $prerequisites = 0,
451
        $max_time_allowed = 0,
452
        $userId = 0
453
    ) {
454
        $course_id = $this->course_info['real_id'];
455
        if (empty($course_id)) {
456
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
457
            $this->course_info = api_get_course_info($this->cc);
458
            $course_id = $this->course_info['real_id'];
459
        }
460
        $userId = empty($userId) ? api_get_user_id() : $userId;
461
        $sessionId = api_get_session_id();
462
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
463
        $_course = $this->course_info;
464
        $parent = (int) $parent;
465
        $previous = (int) $previous;
466
        $id = (int) $id;
467
        $max_time_allowed = (int) $max_time_allowed;
468
        if (empty($max_time_allowed)) {
469
            $max_time_allowed = 0;
470
        }
471
        $sql = "SELECT COUNT(iid) AS num
472
                FROM $tbl_lp_item
473
                WHERE
474
                    c_id = $course_id AND
475
                    lp_id = ".$this->get_id()." AND
476
                    parent_item_id = $parent ";
477
478
        $res_count = Database::query($sql);
479
        $row = Database::fetch_array($res_count);
480
        $num = $row['num'];
481
482
        $tmp_previous = 0;
483
        $display_order = 0;
484
        $next = 0;
485
        if ($num > 0) {
486
            if (empty($previous)) {
487
                $sql = "SELECT iid, next_item_id, display_order
488
                        FROM $tbl_lp_item
489
                        WHERE
490
                            c_id = $course_id AND
491
                            lp_id = ".$this->get_id()." AND
492
                            parent_item_id = $parent AND
493
                            previous_item_id = 0 OR
494
                            previous_item_id = $parent ";
495
                $result = Database::query($sql);
496
                $row = Database::fetch_array($result);
497
                if ($row) {
498
                    $next = $row['iid'];
499
                }
500
            } else {
501
                $previous = (int) $previous;
502
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
503
						FROM $tbl_lp_item
504
                        WHERE
505
                            c_id = $course_id AND
506
                            lp_id = ".$this->get_id()." AND
507
                            iid = $previous";
508
                $result = Database::query($sql);
509
                $row = Database::fetch_array($result);
510
                if ($row) {
511
                    $tmp_previous = $row['iid'];
512
                    $next = $row['next_item_id'];
513
                    $display_order = $row['display_order'];
514
                }
515
            }
516
        }
517
518
        $id = (int) $id;
519
        $typeCleaned = Database::escape_string($type);
520
        $max_score = 100;
521
        if ('quiz' === $type && $id) {
522
            $sql = 'SELECT SUM(ponderation)
523
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
524
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
525
                    ON
526
                        quiz_question.id = quiz_rel_question.question_id AND
527
                        quiz_question.c_id = quiz_rel_question.c_id
528
                    WHERE
529
                        quiz_rel_question.exercice_id = '.$id." AND
530
                        quiz_question.c_id = $course_id AND
531
                        quiz_rel_question.c_id = $course_id ";
532
            $rsQuiz = Database::query($sql);
533
            $max_score = Database::result($rsQuiz, 0, 0);
534
535
            // Disabling the exercise if we add it inside a LP
536
            $exercise = new Exercise($course_id);
537
            $exercise->read($id);
538
            $exercise->disable();
539
            $exercise->save();
540
        }
541
542
        $params = [
543
            'c_id' => $course_id,
544
            'lp_id' => $this->get_id(),
545
            'item_type' => $typeCleaned,
546
            'ref' => '',
547
            'title' => $title,
548
            'description' => $description,
549
            'path' => $id,
550
            'max_score' => $max_score,
551
            'parent_item_id' => $parent,
552
            'previous_item_id' => $previous,
553
            'next_item_id' => (int) $next,
554
            'display_order' => $display_order + 1,
555
            'prerequisite' => $prerequisites,
556
            'max_time_allowed' => $max_time_allowed,
557
            'min_score' => 0,
558
            'launch_data' => '',
559
        ];
560
561
        if (0 != $prerequisites) {
562
            $params['prerequisite'] = $prerequisites;
563
        }
564
565
        $new_item_id = Database::insert($tbl_lp_item, $params);
566
        if ($new_item_id) {
567
            if (!empty($next)) {
568
                $sql = "UPDATE $tbl_lp_item
569
                        SET previous_item_id = $new_item_id
570
                        WHERE c_id = $course_id AND iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
571
                Database::query($sql);
572
            }
573
574
            // Update the item that should be before the new item.
575
            if (!empty($tmp_previous)) {
576
                $sql = "UPDATE $tbl_lp_item
577
                        SET next_item_id = $new_item_id
578
                        WHERE c_id = $course_id AND iid = $tmp_previous";
579
                Database::query($sql);
580
            }
581
582
            // Update all the items after the new item.
583
            $sql = "UPDATE $tbl_lp_item
584
                        SET display_order = display_order + 1
585
                    WHERE
586
                        c_id = $course_id AND
587
                        lp_id = ".$this->get_id()." AND
588
                        iid <> $new_item_id AND
589
                        parent_item_id = $parent AND
590
                        display_order > $display_order";
591
            Database::query($sql);
592
593
            // Update the item that should come after the new item.
594
            $sql = "UPDATE $tbl_lp_item
595
                    SET ref = $new_item_id
596
                    WHERE c_id = $course_id AND iid = $new_item_id";
597
            Database::query($sql);
598
599
            $sql = "UPDATE $tbl_lp_item
600
                    SET previous_item_id = ".$this->getLastInFirstLevel()."
601
                    WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
602
            Database::query($sql);
603
604
            // Upload audio.
605
            if (!empty($_FILES['mp3']['name'])) {
606
                // Create the audio folder if it does not exist yet.
607
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
608
                if (!is_dir($filepath.'audio')) {
609
                    mkdir(
610
                        $filepath.'audio',
611
                        api_get_permissions_for_new_directories()
612
                    );
613
                    $audio_id = DocumentManager::addDocument(
614
                        $_course,
615
                        '/audio',
616
                        'folder',
617
                        0,
618
                        'audio',
619
                        '',
620
                        0,
621
                        true,
622
                        null,
623
                        $sessionId,
624
                        $userId
625
                    );
626
                }
627
628
                $file_path = handle_uploaded_document(
629
                    $_course,
630
                    $_FILES['mp3'],
631
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
632
                    '/audio',
633
                    $userId,
634
                    '',
635
                    '',
636
                    '',
637
                    '',
638
                    false
639
                );
640
641
                // Getting the filename only.
642
                $file_components = explode('/', $file_path);
643
                $file = $file_components[count($file_components) - 1];
644
645
                // Store the mp3 file in the lp_item table.
646
                $sql = "UPDATE $tbl_lp_item SET
647
                          audio = '".Database::escape_string($file)."'
648
                        WHERE iid = '".intval($new_item_id)."'";
649
                Database::query($sql);
650
            }
651
        }
652
653
        return $new_item_id;
654
    }
655
656
    /**
657
     * Static admin function allowing addition of a learnpath to a course.
658
     *
659
     * @param string $courseCode
660
     * @param string $name
661
     * @param string $description
662
     * @param string $learnpath
663
     * @param string $origin
664
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
665
     * @param string $publicated_on
666
     * @param string $expired_on
667
     * @param int    $categoryId
668
     * @param int    $userId
669
     *
670
     * @return CLp
671
     */
672
    public static function add_lp(
673
        $courseCode,
674
        $name,
675
        $description = '',
676
        $learnpath = 'guess',
677
        $origin = 'zip',
678
        $zipname = '',
679
        $publicated_on = '',
680
        $expired_on = '',
681
        $categoryId = 0,
682
        $userId = 0
683
    ) {
684
        global $charset;
685
686
        if (!empty($courseCode)) {
687
            $courseInfo = api_get_course_info($courseCode);
688
            $course_id = $courseInfo['real_id'];
689
        } else {
690
            $course_id = api_get_course_int_id();
691
            $courseInfo = api_get_course_info();
692
        }
693
694
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
695
        // Check course code exists.
696
        // Check lp_name doesn't exist, otherwise append something.
697
        $i = 0;
698
        $categoryId = (int) $categoryId;
699
        // Session id.
700
        $session_id = api_get_session_id();
701
        $userId = empty($userId) ? api_get_user_id() : $userId;
702
703
        if (empty($publicated_on)) {
704
            $publicated_on = null;
705
        } else {
706
            $publicated_on = api_get_utc_datetime($publicated_on, true, true);
707
        }
708
709
        if (empty($expired_on)) {
710
            $expired_on = null;
711
        } else {
712
            $expired_on = api_get_utc_datetime($expired_on, true, true);
713
        }
714
715
        $check_name = "SELECT * FROM $tbl_lp
716
                       WHERE c_id = $course_id AND name = '".Database::escape_string($name)."'";
717
        $res_name = Database::query($check_name);
718
719
        while (Database::num_rows($res_name)) {
720
            // There is already one such name, update the current one a bit.
721
            $i++;
722
            $name = $name.' - '.$i;
723
            $check_name = "SELECT * FROM $tbl_lp
724
                           WHERE c_id = $course_id AND name = '".Database::escape_string($name)."' ";
725
            $res_name = Database::query($check_name);
726
        }
727
        // New name does not exist yet; keep it.
728
        // Escape description.
729
        // Kevin: added htmlentities().
730
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
731
        $type = 1;
732
        switch ($learnpath) {
733
            case 'guess':
734
            case 'aicc':
735
                break;
736
            case 'dokeos':
737
            case 'chamilo':
738
                $type = 1;
739
                break;
740
        }
741
742
        $id = null;
743
        $sessionEntity = api_get_session_entity();
744
        $courseEntity = api_get_course_entity($courseInfo['real_id']);
745
746
        switch ($origin) {
747
            case 'zip':
748
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
749
                break;
750
            case 'manual':
751
            default:
752
                $get_max = "SELECT MAX(display_order)
753
                            FROM $tbl_lp WHERE c_id = $course_id";
754
                $res_max = Database::query($get_max);
755
                if (Database::num_rows($res_max) < 1) {
756
                    $dsp = 1;
757
                } else {
758
                    $row = Database::fetch_array($res_max);
759
                    $dsp = $row[0] + 1;
760
                }
761
762
                $category = null;
763
                if (!empty($categoryId)) {
764
                    $category = Container::getLpCategoryRepository()->find($categoryId);
765
                }
766
767
                $lp = new CLp();
768
                $lp
769
                    ->setCId($course_id)
770
                    ->setLpType($type)
771
                    ->setName($name)
772
                    ->setDescription($description)
773
                    ->setDisplayOrder($dsp)
774
                    ->setSessionId($session_id)
775
                    ->setCategory($category)
776
                    ->setPublicatedOn($publicated_on)
777
                    ->setExpiredOn($expired_on)
778
                    ->setParent($courseEntity)
779
                    ->addCourseLink($courseEntity, $sessionEntity)
780
                ;
781
782
                $em = Database::getManager();
783
                $em->persist($lp);
784
                $em->flush();
785
786
                if ($lp->getIid()) {
787
                    $id = $lp->getIid();
788
                }
789
790
                // Insert into item_property.
791
                /*api_item_property_update(
792
                    $courseInfo,
793
                    TOOL_LEARNPATH,
794
                    $id,
795
                    'LearnpathAdded',
796
                    $userId
797
                );
798
                api_set_default_visibility(
799
                    $id,
800
                    TOOL_LEARNPATH,
801
                    0,
802
                    $courseInfo,
803
                    $session_id,
804
                    $userId
805
                );*/
806
807
                break;
808
        }
809
810
        return $lp;
811
    }
812
813
    /**
814
     * Auto completes the parents of an item in case it's been completed or passed.
815
     *
816
     * @param int $item Optional ID of the item from which to look for parents
817
     */
818
    public function autocomplete_parents($item)
819
    {
820
        $debug = $this->debug;
821
822
        if (empty($item)) {
823
            $item = $this->current;
824
        }
825
826
        $currentItem = $this->getItem($item);
827
        if ($currentItem) {
828
            $parent_id = $currentItem->get_parent();
829
            $parent = $this->getItem($parent_id);
830
            if ($parent) {
831
                // if $item points to an object and there is a parent.
832
                if ($debug) {
833
                    error_log(
834
                        'Autocompleting parent of item '.$item.' '.
835
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
836
                        0
837
                    );
838
                }
839
840
                // New experiment including failed and browsed in completed status.
841
                //$current_status = $currentItem->get_status();
842
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
843
                // Fixes chapter auto complete
844
                if (true) {
845
                    // If the current item is completed or passes or succeeded.
846
                    $updateParentStatus = true;
847
                    if ($debug) {
848
                        error_log('Status of current item is alright');
849
                    }
850
851
                    foreach ($parent->get_children() as $childItemId) {
852
                        $childItem = $this->getItem($childItemId);
853
854
                        // If children was not set try to get the info
855
                        if (empty($childItem->db_item_view_id)) {
856
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
857
                        }
858
859
                        // Check all his brothers (parent's children) for completion status.
860
                        if ($childItemId != $item) {
861
                            if ($debug) {
862
                                error_log(
863
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
864
                                    0
865
                                );
866
                            }
867
                            // Trying completing parents of failed and browsed items as well.
868
                            if ($childItem->status_is(
869
                                [
870
                                    'completed',
871
                                    'passed',
872
                                    'succeeded',
873
                                    'browsed',
874
                                    'failed',
875
                                ]
876
                            )
877
                            ) {
878
                                // Keep completion status to true.
879
                                continue;
880
                            } else {
881
                                if ($debug > 2) {
882
                                    error_log(
883
                                        '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,
884
                                        0
885
                                    );
886
                                }
887
                                $updateParentStatus = false;
888
                                break;
889
                            }
890
                        }
891
                    }
892
893
                    if ($updateParentStatus) {
894
                        // If all the children were completed:
895
                        $parent->set_status('completed');
896
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
897
                        // Force the status to "completed"
898
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
899
                        $this->update_queue[$parent->get_id()] = 'completed';
900
                        if ($debug) {
901
                            error_log(
902
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
903
                                print_r($this->update_queue, 1),
904
                                0
905
                            );
906
                        }
907
                        // Recursive call.
908
                        $this->autocomplete_parents($parent->get_id());
909
                    }
910
                }
911
            } else {
912
                if ($debug) {
913
                    error_log("Parent #$parent_id does not exists");
914
                }
915
            }
916
        } else {
917
            if ($debug) {
918
                error_log("#$item is an item that doesn't have parents");
919
            }
920
        }
921
    }
922
923
    /**
924
     * Closes the current resource.
925
     *
926
     * Stops the timer
927
     * Saves into the database if required
928
     * Clears the current resource data from this object
929
     *
930
     * @return bool True on success, false on failure
931
     */
932
    public function close()
933
    {
934
        if (empty($this->lp_id)) {
935
            $this->error = 'Trying to close this learnpath but no ID is set';
936
937
            return false;
938
        }
939
        $this->current_time_stop = time();
940
        $this->ordered_items = [];
941
        $this->index = 0;
942
        unset($this->lp_id);
943
        //unset other stuff
944
        return true;
945
    }
946
947
    /**
948
     * Static admin function allowing removal of a learnpath.
949
     *
950
     * @param array  $courseInfo
951
     * @param int    $id         Learnpath ID
952
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
953
     *
954
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
955
     */
956
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
957
    {
958
        $course_id = api_get_course_int_id();
959
        if (!empty($courseInfo)) {
960
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
961
        }
962
963
        // TODO: Implement a way of getting this to work when the current object is not set.
964
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
965
        // If an ID is specifically given and the current LP is not the same, prevent delete.
966
        if (!empty($id) && ($id != $this->lp_id)) {
967
            return false;
968
        }
969
970
        $lp = Database::get_course_table(TABLE_LP_MAIN);
971
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
972
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
973
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
974
975
        // Delete lp item id.
976
        foreach ($this->items as $lpItemId => $dummy) {
977
            $sql = "DELETE FROM $lp_item_view
978
                    WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
979
            Database::query($sql);
980
        }
981
982
        // Proposed by Christophe (nickname: clefevre)
983
        $sql = "DELETE FROM $lp_item
984
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
985
        Database::query($sql);
986
987
        $sql = "DELETE FROM $lp_view
988
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
989
        Database::query($sql);
990
991
        self::toggleVisibility($this->lp_id, 0);
992
993
        if (2 == $this->type || 3 == $this->type) {
994
            // This is a scorm learning path, delete the files as well.
995
            $sql = "SELECT path FROM $lp
996
                    WHERE iid = ".$this->lp_id;
997
            $res = Database::query($sql);
998
            if (Database::num_rows($res) > 0) {
999
                $row = Database::fetch_array($res);
1000
                $path = $row['path'];
1001
                $sql = "SELECT id FROM $lp
1002
                        WHERE
1003
                            c_id = $course_id AND
1004
                            path = '$path' AND
1005
                            iid != ".$this->lp_id;
1006
                $res = Database::query($sql);
1007
                if (Database::num_rows($res) > 0) {
1008
                    // Another learning path uses this directory, so don't delete it.
1009
                    if ($this->debug > 2) {
1010
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1011
                    }
1012
                } else {
1013
                    // No other LP uses that directory, delete it.
1014
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1015
                    // The absolute system path for this course.
1016
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
1017
                    if ('remove' == $delete && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1018
                        if ($this->debug > 2) {
1019
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1020
                        }
1021
                        // Proposed by Christophe (clefevre).
1022
                        if (0 == strcmp(substr($path, -2), "/.")) {
1023
                            $path = substr($path, 0, -1); // Remove "." at the end.
1024
                        }
1025
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1026
                        rmdirr($course_scorm_dir.$path);
1027
                    }
1028
                }
1029
            }
1030
        }
1031
1032
       if (api_get_configuration_value('allow_lp_subscription_to_usergroups')) {
1033
            $table = Database::get_course_table(TABLE_LP_REL_USERGROUP);
1034
            $sql = "DELETE FROM $table
1035
                    WHERE
1036
                        lp_id = {$this->lp_id} AND
1037
                        c_id = $course_id ";
1038
            Database::query($sql);
1039
        }
1040
1041
        /*$tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1042
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1043
        // Delete tools
1044
        $sql = "DELETE FROM $tbl_tool
1045
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1046
        Database::query($sql);*/
1047
1048
        /*$sql = "DELETE FROM $lp
1049
                WHERE iid = ".$this->lp_id;
1050
        Database::query($sql);*/
1051
        $repo = Container::getLpRepository();
1052
        $lp = $repo->find($this->lp_id);
1053
        Database::getManager()->remove($lp);
1054
        Database::getManager()->flush();
1055
1056
        // Updates the display order of all lps.
1057
        $this->update_display_order();
1058
1059
        /*api_item_property_update(
1060
            api_get_course_info(),
1061
            TOOL_LEARNPATH,
1062
            $this->lp_id,
1063
            'delete',
1064
            api_get_user_id()
1065
        );*/
1066
1067
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1068
            api_get_course_id(),
1069
            4,
1070
            $id,
1071
            api_get_session_id()
1072
        );
1073
1074
        if (false !== $link_info) {
1075
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1076
        }
1077
1078
        if ('true' == api_get_setting('search_enabled')) {
1079
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1080
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1081
        }
1082
    }
1083
1084
    /**
1085
     * Removes all the children of one item - dangerous!
1086
     *
1087
     * @param int $id Element ID of which children have to be removed
1088
     *
1089
     * @return int Total number of children removed
1090
     */
1091
    public function delete_children_items($id)
1092
    {
1093
        $course_id = $this->course_info['real_id'];
1094
1095
        $num = 0;
1096
        $id = (int) $id;
1097
        if (empty($id) || empty($course_id)) {
1098
            return false;
1099
        }
1100
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1101
        $sql = "SELECT * FROM $lp_item
1102
                WHERE c_id = $course_id AND parent_item_id = $id";
1103
        $res = Database::query($sql);
1104
        while ($row = Database::fetch_array($res)) {
1105
            $num += $this->delete_children_items($row['iid']);
1106
            $sql = "DELETE FROM $lp_item
1107
                    WHERE c_id = $course_id AND iid = ".$row['iid'];
1108
            Database::query($sql);
1109
            $num++;
1110
        }
1111
1112
        return $num;
1113
    }
1114
1115
    /**
1116
     * Removes an item from the current learnpath.
1117
     *
1118
     * @param int $id Elem ID (0 if first)
1119
     *
1120
     * @return int Number of elements moved
1121
     *
1122
     * @todo implement resource removal
1123
     */
1124
    public function delete_item($id)
1125
    {
1126
        $course_id = api_get_course_int_id();
1127
        $id = (int) $id;
1128
        // TODO: Implement the resource removal.
1129
        if (empty($id) || empty($course_id)) {
1130
            return false;
1131
        }
1132
        // First select item to get previous, next, and display order.
1133
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1134
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1135
        $res_sel = Database::query($sql_sel);
1136
        if (Database::num_rows($res_sel) < 1) {
1137
            return false;
1138
        }
1139
        $row = Database::fetch_array($res_sel);
1140
        $previous = $row['previous_item_id'];
1141
        $next = $row['next_item_id'];
1142
        $display = $row['display_order'];
1143
        $parent = $row['parent_item_id'];
1144
        $lp = $row['lp_id'];
1145
        // Delete children items.
1146
        $this->delete_children_items($id);
1147
        // Now delete the item.
1148
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1149
        Database::query($sql_del);
1150
        // Now update surrounding items.
1151
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1152
                    WHERE iid = $previous";
1153
        Database::query($sql_upd);
1154
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1155
                    WHERE iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
1156
        Database::query($sql_upd);
1157
        // Now update all following items with new display order.
1158
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1159
                    WHERE
1160
                        c_id = $course_id AND
1161
                        lp_id = $lp AND
1162
                        parent_item_id = $parent AND
1163
                        display_order > $display";
1164
        Database::query($sql_all);
1165
1166
        //Removing prerequisites since the item will not longer exist
1167
        $sql_all = "UPDATE $lp_item SET prerequisite = ''
1168
                    WHERE c_id = $course_id AND prerequisite = '$id'";
1169
        Database::query($sql_all);
1170
1171
        $sql = "UPDATE $lp_item
1172
                SET previous_item_id = ".$this->getLastInFirstLevel()."
1173
                WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
1174
        Database::query($sql);
1175
1176
        // Remove from search engine if enabled.
1177
        if ('true' === api_get_setting('search_enabled')) {
1178
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1179
            $sql = 'SELECT * FROM %s
1180
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1181
                    LIMIT 1';
1182
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1183
            $res = Database::query($sql);
1184
            if (Database::num_rows($res) > 0) {
1185
                $row2 = Database::fetch_array($res);
1186
                $di = new ChamiloIndexer();
1187
                $di->remove_document($row2['search_did']);
1188
            }
1189
            $sql = 'DELETE FROM %s
1190
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1191
                    LIMIT 1';
1192
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1193
            Database::query($sql);
1194
        }
1195
    }
1196
1197
    /**
1198
     * Updates an item's content in place.
1199
     *
1200
     * @param int    $id               Element ID
1201
     * @param int    $parent           Parent item ID
1202
     * @param int    $previous         Previous item ID
1203
     * @param string $title            Item title
1204
     * @param string $description      Item description
1205
     * @param string $prerequisites    Prerequisites (optional)
1206
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1207
     * @param int    $max_time_allowed
1208
     * @param string $url
1209
     *
1210
     * @return bool True on success, false on error
1211
     */
1212
    public function edit_item(
1213
        $id,
1214
        $parent,
1215
        $previous,
1216
        $title,
1217
        $description,
1218
        $prerequisites = '0',
1219
        $audio = [],
1220
        $max_time_allowed = 0,
1221
        $url = ''
1222
    ) {
1223
        $course_id = api_get_course_int_id();
1224
        $_course = api_get_course_info();
1225
        $id = (int) $id;
1226
1227
        if (empty($max_time_allowed)) {
1228
            $max_time_allowed = 0;
1229
        }
1230
1231
        if (empty($id) || empty($_course)) {
1232
            return false;
1233
        }
1234
1235
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1236
        $sql = "SELECT * FROM $tbl_lp_item
1237
                WHERE iid = $id";
1238
        $res_select = Database::query($sql);
1239
        $row_select = Database::fetch_array($res_select);
1240
        $audio_update_sql = '';
1241
        if (is_array($audio) && !empty($audio['tmp_name']) && 0 === $audio['error']) {
1242
            // Create the audio folder if it does not exist yet.
1243
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1244
            if (!is_dir($filepath.'audio')) {
1245
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1246
                $audio_id = DocumentManager::addDocument(
1247
                    $_course,
1248
                    '/audio',
1249
                    'folder',
1250
                    0,
1251
                    'audio'
1252
                );
1253
            }
1254
1255
            // Upload file in documents.
1256
            $pi = pathinfo($audio['name']);
1257
            if ('mp3' === $pi['extension']) {
1258
                $c_det = api_get_course_info($this->cc);
1259
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1260
                $path = handle_uploaded_document(
1261
                    $c_det,
1262
                    $audio,
1263
                    $bp,
1264
                    '/audio',
1265
                    api_get_user_id(),
1266
                    0,
1267
                    null,
1268
                    0,
1269
                    'rename',
1270
                    false,
1271
                    0
1272
                );
1273
                $path = substr($path, 7);
1274
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1275
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1276
            }
1277
        }
1278
1279
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1280
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1281
1282
        // TODO: htmlspecialchars to be checked for encoding related problems.
1283
        if ($same_parent && $same_previous) {
1284
            // Only update title and description.
1285
            $sql = "UPDATE $tbl_lp_item
1286
                    SET title = '".Database::escape_string($title)."',
1287
                        prerequisite = '".$prerequisites."',
1288
                        description = '".Database::escape_string($description)."'
1289
                        ".$audio_update_sql.",
1290
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1291
                    WHERE iid = $id";
1292
            Database::query($sql);
1293
        } else {
1294
            $old_parent = $row_select['parent_item_id'];
1295
            $old_previous = $row_select['previous_item_id'];
1296
            $old_next = $row_select['next_item_id'];
1297
            $old_order = $row_select['display_order'];
1298
            $old_prerequisite = $row_select['prerequisite'];
1299
            $old_max_time_allowed = $row_select['max_time_allowed'];
1300
1301
            /* BEGIN -- virtually remove the current item id */
1302
            /* for the next and previous item it is like the current item doesn't exist anymore */
1303
            if (0 != $old_previous) {
1304
                // Next
1305
                $sql = "UPDATE $tbl_lp_item
1306
                        SET next_item_id = $old_next
1307
                        WHERE iid = $old_previous";
1308
                Database::query($sql);
1309
            }
1310
1311
            if (!empty($old_next)) {
1312
                // Previous
1313
                $sql = "UPDATE $tbl_lp_item
1314
                        SET previous_item_id = $old_previous
1315
                        WHERE iid = $old_next";
1316
                Database::query($sql);
1317
            }
1318
1319
            // display_order - 1 for every item with a display_order
1320
            // bigger then the display_order of the current item.
1321
            $sql = "UPDATE $tbl_lp_item
1322
                    SET display_order = display_order - 1
1323
                    WHERE
1324
                        c_id = $course_id AND
1325
                        display_order > $old_order AND
1326
                        lp_id = ".$this->lp_id." AND
1327
                        parent_item_id = $old_parent";
1328
            Database::query($sql);
1329
            /* END -- virtually remove the current item id */
1330
1331
            /* BEGIN -- update the current item id to his new location */
1332
            if (0 == $previous) {
1333
                // Select the data of the item that should come after the current item.
1334
                $sql = "SELECT id, display_order
1335
                        FROM $tbl_lp_item
1336
                        WHERE
1337
                            c_id = $course_id AND
1338
                            lp_id = ".$this->lp_id." AND
1339
                            parent_item_id = $parent AND
1340
                            previous_item_id = $previous";
1341
                $res_select_old = Database::query($sql);
1342
                $row_select_old = Database::fetch_array($res_select_old);
1343
1344
                // If the new parent didn't have children before.
1345
                if (0 == Database::num_rows($res_select_old)) {
1346
                    $new_next = 0;
1347
                    $new_order = 1;
1348
                } else {
1349
                    $new_next = $row_select_old['id'];
1350
                    $new_order = $row_select_old['display_order'];
1351
                }
1352
            } else {
1353
                // Select the data of the item that should come before the current item.
1354
                $sql = "SELECT next_item_id, display_order
1355
                        FROM $tbl_lp_item
1356
                        WHERE iid = $previous";
1357
                $res_select_old = Database::query($sql);
1358
                $row_select_old = Database::fetch_array($res_select_old);
1359
                $new_next = $row_select_old['next_item_id'];
1360
                $new_order = $row_select_old['display_order'] + 1;
1361
            }
1362
1363
            // TODO: htmlspecialchars to be checked for encoding related problems.
1364
            // Update the current item with the new data.
1365
            $sql = "UPDATE $tbl_lp_item
1366
                    SET
1367
                        title = '".Database::escape_string($title)."',
1368
                        description = '".Database::escape_string($description)."',
1369
                        parent_item_id = $parent,
1370
                        previous_item_id = $previous,
1371
                        next_item_id = $new_next,
1372
                        display_order = $new_order
1373
                        $audio_update_sql
1374
                    WHERE iid = $id";
1375
            Database::query($sql);
1376
1377
            if (0 != $previous) {
1378
                // Update the previous item's next_item_id.
1379
                $sql = "UPDATE $tbl_lp_item
1380
                        SET next_item_id = $id
1381
                        WHERE iid = $previous";
1382
                Database::query($sql);
1383
            }
1384
1385
            if (!empty($new_next)) {
1386
                // Update the next item's previous_item_id.
1387
                $sql = "UPDATE $tbl_lp_item
1388
                        SET previous_item_id = $id
1389
                        WHERE iid = $new_next";
1390
                Database::query($sql);
1391
            }
1392
1393
            if ($old_prerequisite != $prerequisites) {
1394
                $sql = "UPDATE $tbl_lp_item
1395
                        SET prerequisite = '$prerequisites'
1396
                        WHERE iid = $id";
1397
                Database::query($sql);
1398
            }
1399
1400
            if ($old_max_time_allowed != $max_time_allowed) {
1401
                // update max time allowed
1402
                $sql = "UPDATE $tbl_lp_item
1403
                        SET max_time_allowed = $max_time_allowed
1404
                        WHERE iid = $id";
1405
                Database::query($sql);
1406
            }
1407
1408
            // Update all the items with the same or a bigger display_order than the current item.
1409
            $sql = "UPDATE $tbl_lp_item
1410
                    SET display_order = display_order + 1
1411
                    WHERE
1412
                       c_id = $course_id AND
1413
                       lp_id = ".$this->get_id()." AND
1414
                       iid <> $id AND
1415
                       parent_item_id = $parent AND
1416
                       display_order >= $new_order";
1417
            Database::query($sql);
1418
        }
1419
1420
        if ('link' == $row_select['item_type']) {
1421
            $link = new Link();
1422
            $linkId = $row_select['path'];
1423
            $link->updateLink($linkId, $url);
1424
        }
1425
    }
1426
1427
    /**
1428
     * Updates an item's prereq in place.
1429
     *
1430
     * @param int    $id              Element ID
1431
     * @param string $prerequisite_id Prerequisite Element ID
1432
     * @param int    $minScore        Prerequisite min score
1433
     * @param int    $maxScore        Prerequisite max score
1434
     *
1435
     * @return bool True on success, false on error
1436
     */
1437
    public function edit_item_prereq(
1438
        $id,
1439
        $prerequisite_id,
1440
        $minScore = 0,
1441
        $maxScore = 100
1442
    ) {
1443
        $id = (int) $id;
1444
1445
        if (empty($id)) {
1446
            return false;
1447
        }
1448
        $prerequisite_id = (int) $prerequisite_id;
1449
1450
        if (empty($minScore) || $minScore < 0) {
1451
            $minScore = 0;
1452
        }
1453
1454
        if (empty($maxScore) || $maxScore < 0) {
1455
            $maxScore = 100;
1456
        }
1457
1458
        $minScore = (float) $minScore;
1459
        $maxScore = (float) $maxScore;
1460
1461
        if (empty($prerequisite_id)) {
1462
            $prerequisite_id = 'NULL';
1463
            $minScore = 0;
1464
            $maxScore = 100;
1465
        }
1466
1467
        $table = Database::get_course_table(TABLE_LP_ITEM);
1468
        $sql = " UPDATE $table
1469
                 SET
1470
                    prerequisite = $prerequisite_id ,
1471
                    prerequisite_min_score = $minScore ,
1472
                    prerequisite_max_score = $maxScore
1473
                 WHERE iid = $id";
1474
        Database::query($sql);
1475
1476
        return true;
1477
    }
1478
1479
    /**
1480
     * Get the specific prefix index terms of this learning path.
1481
     *
1482
     * @param string $prefix
1483
     *
1484
     * @return array Array of terms
1485
     */
1486
    public function get_common_index_terms_by_prefix($prefix)
1487
    {
1488
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1489
        $terms = get_specific_field_values_list_by_prefix(
1490
            $prefix,
1491
            $this->cc,
1492
            TOOL_LEARNPATH,
1493
            $this->lp_id
1494
        );
1495
        $prefix_terms = [];
1496
        if (!empty($terms)) {
1497
            foreach ($terms as $term) {
1498
                $prefix_terms[] = $term['value'];
1499
            }
1500
        }
1501
1502
        return $prefix_terms;
1503
    }
1504
1505
    /**
1506
     * Gets the number of items currently completed.
1507
     *
1508
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1509
     *
1510
     * @return int The number of items currently completed
1511
     */
1512
    public function get_complete_items_count($failedStatusException = false)
1513
    {
1514
        $i = 0;
1515
        $completedStatusList = [
1516
            'completed',
1517
            'passed',
1518
            'succeeded',
1519
            'browsed',
1520
        ];
1521
1522
        if (!$failedStatusException) {
1523
            $completedStatusList[] = 'failed';
1524
        }
1525
1526
        foreach ($this->items as $id => $dummy) {
1527
            // Trying failed and browsed considered "progressed" as well.
1528
            if ($this->items[$id]->status_is($completedStatusList) &&
1529
                'dir' != $this->items[$id]->get_type()
1530
            ) {
1531
                $i++;
1532
            }
1533
        }
1534
1535
        return $i;
1536
    }
1537
1538
    /**
1539
     * Gets the current item ID.
1540
     *
1541
     * @return int The current learnpath item id
1542
     */
1543
    public function get_current_item_id()
1544
    {
1545
        $current = 0;
1546
        if (!empty($this->current)) {
1547
            $current = (int) $this->current;
1548
        }
1549
1550
        return $current;
1551
    }
1552
1553
    /**
1554
     * Force to get the first learnpath item id.
1555
     *
1556
     * @return int The current learnpath item id
1557
     */
1558
    public function get_first_item_id()
1559
    {
1560
        $current = 0;
1561
        if (is_array($this->ordered_items)) {
1562
            $current = $this->ordered_items[0];
1563
        }
1564
1565
        return $current;
1566
    }
1567
1568
    /**
1569
     * Gets the total number of items available for viewing in this SCORM.
1570
     *
1571
     * @return int The total number of items
1572
     */
1573
    public function get_total_items_count()
1574
    {
1575
        return count($this->items);
1576
    }
1577
1578
    /**
1579
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1580
     *
1581
     * @return int The total no-chapters number of items
1582
     */
1583
    public function getTotalItemsCountWithoutDirs()
1584
    {
1585
        $total = 0;
1586
        $typeListNotToCount = self::getChapterTypes();
1587
        foreach ($this->items as $temp2) {
1588
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1589
                $total++;
1590
            }
1591
        }
1592
1593
        return $total;
1594
    }
1595
1596
    /**
1597
     *  Sets the first element URL.
1598
     */
1599
    public function first()
1600
    {
1601
        if ($this->debug > 0) {
1602
            error_log('In learnpath::first()', 0);
1603
            error_log('$this->last_item_seen '.$this->last_item_seen);
1604
        }
1605
1606
        // Test if the last_item_seen exists and is not a dir.
1607
        if (0 == count($this->ordered_items)) {
1608
            $this->index = 0;
1609
        }
1610
1611
        if (!empty($this->last_item_seen) &&
1612
            !empty($this->items[$this->last_item_seen]) &&
1613
            'dir' != $this->items[$this->last_item_seen]->get_type()
1614
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1615
            //&& !$this->items[$this->last_item_seen]->is_done()
1616
        ) {
1617
            if ($this->debug > 2) {
1618
                error_log(
1619
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1620
                    $this->items[$this->last_item_seen]->get_type()
1621
                );
1622
            }
1623
            $index = -1;
1624
            foreach ($this->ordered_items as $myindex => $item_id) {
1625
                if ($item_id == $this->last_item_seen) {
1626
                    $index = $myindex;
1627
                    break;
1628
                }
1629
            }
1630
            if (-1 == $index) {
1631
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1632
                if ($this->debug > 2) {
1633
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1634
                }
1635
1636
                return false;
1637
            } else {
1638
                $this->last = $this->last_item_seen;
1639
                $this->current = $this->last_item_seen;
1640
                $this->index = $index;
1641
            }
1642
        } else {
1643
            if ($this->debug > 2) {
1644
                error_log('In learnpath::first() - No last item seen', 0);
1645
            }
1646
            $index = 0;
1647
            // Loop through all ordered items and stop at the first item that is
1648
            // not a directory *and* that has not been completed yet.
1649
            while (!empty($this->ordered_items[$index]) &&
1650
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1651
                (
1652
                    'dir' == $this->items[$this->ordered_items[$index]]->get_type() ||
1653
                    true === $this->items[$this->ordered_items[$index]]->is_done()
1654
                ) && $index < $this->max_ordered_items) {
1655
                $index++;
1656
            }
1657
1658
            $this->last = $this->current;
1659
            // current is
1660
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1661
            $this->index = $index;
1662
            if ($this->debug > 2) {
1663
                error_log('$index '.$index);
1664
                error_log('In learnpath::first() - No last item seen');
1665
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1666
            }
1667
        }
1668
        if ($this->debug > 2) {
1669
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1670
        }
1671
    }
1672
1673
    /**
1674
     * Gets the js library from the database.
1675
     *
1676
     * @return string The name of the javascript library to be used
1677
     */
1678
    public function get_js_lib()
1679
    {
1680
        $lib = '';
1681
        if (!empty($this->js_lib)) {
1682
            $lib = $this->js_lib;
1683
        }
1684
1685
        return $lib;
1686
    }
1687
1688
    /**
1689
     * Gets the learnpath database ID.
1690
     *
1691
     * @return int Learnpath ID in the lp table
1692
     */
1693
    public function get_id()
1694
    {
1695
        if (!empty($this->lp_id)) {
1696
            return (int) $this->lp_id;
1697
        }
1698
1699
        return 0;
1700
    }
1701
1702
    /**
1703
     * Gets the last element URL.
1704
     *
1705
     * @return string URL to load into the viewer
1706
     */
1707
    public function get_last()
1708
    {
1709
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1710
        if (count($this->ordered_items) > 0) {
1711
            $this->index = count($this->ordered_items) - 1;
1712
1713
            return $this->ordered_items[$this->index];
1714
        }
1715
1716
        return false;
1717
    }
1718
1719
    /**
1720
     * Get the last element in the first level.
1721
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1722
     *
1723
     * @return mixed
1724
     */
1725
    public function getLastInFirstLevel()
1726
    {
1727
        try {
1728
            $lastId = Database::getManager()
1729
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1730
                WHERE i.lpId = :lp AND i.parentItemId = 0 AND i.itemType != :type ORDER BY i.displayOrder DESC')
1731
                ->setMaxResults(1)
1732
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1733
                ->getSingleScalarResult();
1734
1735
            return $lastId;
1736
        } catch (Exception $exception) {
1737
            return 0;
1738
        }
1739
    }
1740
1741
    /**
1742
     * Get the learning path name by id.
1743
     *
1744
     * @param int $lpId
1745
     *
1746
     * @return mixed
1747
     */
1748
    public static function getLpNameById($lpId)
1749
    {
1750
        $em = Database::getManager();
1751
1752
        return $em->createQuery('SELECT clp.name FROM ChamiloCourseBundle:CLp clp
1753
            WHERE clp.iid = :iid')
1754
            ->setParameter('iid', $lpId)
1755
            ->getSingleScalarResult();
1756
    }
1757
1758
    /**
1759
     * Gets the navigation bar for the learnpath display screen.
1760
     *
1761
     * @param string $barId
1762
     *
1763
     * @return string The HTML string to use as a navigation bar
1764
     */
1765
    public function get_navigation_bar($barId = '')
1766
    {
1767
        if (empty($barId)) {
1768
            $barId = 'control-top';
1769
        }
1770
        $lpId = $this->lp_id;
1771
        $mycurrentitemid = $this->get_current_item_id();
1772
1773
        $reportingText = get_lang('Reporting');
1774
        $previousText = get_lang('Previous');
1775
        $nextText = get_lang('Next');
1776
        $fullScreenText = get_lang('Back to normal screen');
1777
1778
        $settings = api_get_configuration_value('lp_view_settings');
1779
        $display = isset($settings['display']) ? $settings['display'] : false;
1780
        $reportingIcon = '
1781
            <a class="icon-toolbar"
1782
                id="stats_link"
1783
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
1784
                onclick="window.parent.API.save_asset(); return true;"
1785
                target="content_name" title="'.$reportingText.'">
1786
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1787
            </a>';
1788
1789
        if (!empty($display)) {
1790
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1791
            if (false === $showReporting) {
1792
                $reportingIcon = '';
1793
            }
1794
        }
1795
1796
        $hideArrows = false;
1797
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1798
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1799
        }
1800
1801
        $previousIcon = '';
1802
        $nextIcon = '';
1803
        if (false === $hideArrows) {
1804
            $previousIcon = '
1805
                <a class="icon-toolbar" id="scorm-previous" href="#"
1806
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1807
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1808
                </a>';
1809
1810
            $nextIcon = '
1811
                <a class="icon-toolbar" id="scorm-next" href="#"
1812
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1813
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1814
                </a>';
1815
        }
1816
1817
        if ('fullscreen' === $this->mode) {
1818
            $navbar = '
1819
                  <span id="'.$barId.'" class="buttons">
1820
                    '.$reportingIcon.'
1821
                    '.$previousIcon.'
1822
                    '.$nextIcon.'
1823
                    <a class="icon-toolbar" id="view-embedded"
1824
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1825
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1826
                    </a>
1827
                  </span>';
1828
        } else {
1829
            $navbar = '
1830
                 <span id="'.$barId.'" class="buttons text-right">
1831
                    '.$reportingIcon.'
1832
                    '.$previousIcon.'
1833
                    '.$nextIcon.'
1834
                </span>';
1835
        }
1836
1837
        return $navbar;
1838
    }
1839
1840
    /**
1841
     * Gets the next resource in queue (url).
1842
     *
1843
     * @return string URL to load into the viewer
1844
     */
1845
    public function get_next_index()
1846
    {
1847
        // TODO
1848
        $index = $this->index;
1849
        $index++;
1850
        while (
1851
            !empty($this->ordered_items[$index]) && ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) &&
1852
            $index < $this->max_ordered_items
1853
        ) {
1854
            $index++;
1855
            if ($index == $this->max_ordered_items) {
1856
                if ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) {
1857
                    return $this->index;
1858
                }
1859
1860
                return $index;
1861
            }
1862
        }
1863
        if (empty($this->ordered_items[$index])) {
1864
            return $this->index;
1865
        }
1866
1867
        return $index;
1868
    }
1869
1870
    /**
1871
     * Gets item_id for the next element.
1872
     *
1873
     * @return int Next item (DB) ID
1874
     */
1875
    public function get_next_item_id()
1876
    {
1877
        $new_index = $this->get_next_index();
1878
        if (!empty($new_index)) {
1879
            if (isset($this->ordered_items[$new_index])) {
1880
                return $this->ordered_items[$new_index];
1881
            }
1882
        }
1883
1884
        return 0;
1885
    }
1886
1887
    /**
1888
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
1889
     *
1890
     * Generally, the package provided is in the form of a zip file, so the function
1891
     * has been written to test a zip file. If not a zip, the function will return the
1892
     * default return value: ''
1893
     *
1894
     * @param string $file_path the path to the file
1895
     * @param string $file_name the original name of the file
1896
     *
1897
     * @return string 'scorm','aicc','scorm2004','dokeos', 'error-empty-package' if the package is empty, or '' if the package cannot be recognized
1898
     */
1899
    public static function getPackageType($file_path, $file_name)
1900
    {
1901
        // Get name of the zip file without the extension.
1902
        $file_info = pathinfo($file_name);
1903
        $extension = $file_info['extension']; // Extension only.
1904
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
1905
                'dll',
1906
                'exe',
1907
            ])) {
1908
            return 'oogie';
1909
        }
1910
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
1911
                'dll',
1912
                'exe',
1913
            ])) {
1914
            return 'woogie';
1915
        }
1916
1917
        $zipFile = new PclZip($file_path);
1918
        // Check the zip content (real size and file extension).
1919
        $zipContentArray = $zipFile->listContent();
1920
        $package_type = '';
1921
        $manifest = '';
1922
        $aicc_match_crs = 0;
1923
        $aicc_match_au = 0;
1924
        $aicc_match_des = 0;
1925
        $aicc_match_cst = 0;
1926
        $countItems = 0;
1927
1928
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
1929
        if (is_array($zipContentArray)) {
1930
            $countItems = count($zipContentArray);
1931
            if ($countItems > 0) {
1932
                foreach ($zipContentArray as $thisContent) {
1933
                    if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
1934
                        // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
1935
                    } elseif (false !== stristr($thisContent['filename'], 'imsmanifest.xml')) {
1936
                        $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
1937
                        $package_type = 'scorm';
1938
                        break; // Exit the foreach loop.
1939
                    } elseif (
1940
                        preg_match('/aicc\//i', $thisContent['filename']) ||
1941
                        in_array(
1942
                            strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
1943
                            ['crs', 'au', 'des', 'cst']
1944
                        )
1945
                    ) {
1946
                        $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
1947
                        switch ($ext) {
1948
                            case 'crs':
1949
                                $aicc_match_crs = 1;
1950
                                break;
1951
                            case 'au':
1952
                                $aicc_match_au = 1;
1953
                                break;
1954
                            case 'des':
1955
                                $aicc_match_des = 1;
1956
                                break;
1957
                            case 'cst':
1958
                                $aicc_match_cst = 1;
1959
                                break;
1960
                            default:
1961
                                break;
1962
                        }
1963
                        //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
1964
                    } else {
1965
                        $package_type = '';
1966
                    }
1967
                }
1968
            }
1969
        }
1970
1971
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
1972
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
1973
            $package_type = 'aicc';
1974
        }
1975
1976
        // Try with chamilo course builder
1977
        if (empty($package_type)) {
1978
            // Sometimes users will try to upload an empty zip, or a zip with
1979
            // only a folder. Catch that and make the calling function aware.
1980
            // If the single file was the imsmanifest.xml, then $package_type
1981
            // would be 'scorm' and we wouldn't be here.
1982
            if ($countItems < 2) {
1983
                return 'error-empty-package';
1984
            }
1985
            $package_type = 'chamilo';
1986
        }
1987
1988
        return $package_type;
1989
    }
1990
1991
    /**
1992
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
1993
     *
1994
     * @return string URL to load into the viewer
1995
     */
1996
    public function get_previous_index()
1997
    {
1998
        $index = $this->index;
1999
        if (isset($this->ordered_items[$index - 1])) {
2000
            $index--;
2001
            while (isset($this->ordered_items[$index]) &&
2002
                ('dir' == $this->items[$this->ordered_items[$index]]->get_type())
2003
            ) {
2004
                $index--;
2005
                if ($index < 0) {
2006
                    return $this->index;
2007
                }
2008
            }
2009
        }
2010
2011
        return $index;
2012
    }
2013
2014
    /**
2015
     * Gets item_id for the next element.
2016
     *
2017
     * @return int Previous item (DB) ID
2018
     */
2019
    public function get_previous_item_id()
2020
    {
2021
        $index = $this->get_previous_index();
2022
2023
        return $this->ordered_items[$index];
2024
    }
2025
2026
    /**
2027
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2028
     *
2029
     * @param int    $lpItemId
2030
     * @param string $autostart
2031
     *
2032
     * @return string The mediaplayer HTML
2033
     */
2034
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2035
    {
2036
        $course_id = api_get_course_int_id();
2037
        $courseInfo = api_get_course_info();
2038
        $lpItemId = (int) $lpItemId;
2039
2040
        if (empty($courseInfo) || empty($lpItemId)) {
2041
            return '';
2042
        }
2043
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
2044
2045
        if (empty($item)) {
2046
            return '';
2047
        }
2048
2049
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2050
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2051
        $itemViewId = (int) $item->db_item_view_id;
2052
2053
        // Getting all the information about the item.
2054
        $sql = "SELECT lp_view.status
2055
                FROM $tbl_lp_item as lpi
2056
                INNER JOIN $tbl_lp_item_view as lp_view
2057
                ON (lpi.iid = lp_view.lp_item_id)
2058
                WHERE
2059
                    lp_view.iid = $itemViewId AND
2060
                    lpi.iid = $lpItemId AND
2061
                    lp_view.c_id = $course_id";
2062
        $result = Database::query($sql);
2063
        $row = Database::fetch_assoc($result);
2064
        $output = '';
2065
        $audio = $item->audio;
2066
2067
        if (!empty($audio)) {
2068
            $list = $_SESSION['oLP']->get_toc();
2069
2070
            switch ($item->get_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
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'.$audio;
2095
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document'.$audio.'?'.api_get_cidreq();
2096
2097
            $player = Display::getMediaPlayer(
2098
                $file,
2099
                [
2100
                    'id' => 'lp_audio_media_player',
2101
                    'url' => $url,
2102
                    'autoplay' => $autostart_audio,
2103
                    'width' => '100%',
2104
                ]
2105
            );
2106
2107
            // The mp3 player.
2108
            $output = '<div id="container">';
2109
            $output .= $player;
2110
            $output .= '</div>';
2111
        }
2112
2113
        return $output;
2114
    }
2115
2116
    /**
2117
     * @param int   $studentId
2118
     * @param int   $prerequisite
2119
     * @param array $courseInfo
2120
     * @param int   $sessionId
2121
     *
2122
     * @return bool
2123
     */
2124
    public static function isBlockedByPrerequisite(
2125
        $studentId,
2126
        $prerequisite,
2127
        $courseInfo,
2128
        $sessionId
2129
    ) {
2130
        if (empty($courseInfo)) {
2131
            return false;
2132
        }
2133
2134
        $courseId = $courseInfo['real_id'];
2135
2136
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2137
        if ($allow) {
2138
            if (api_is_allowed_to_edit() ||
2139
                api_is_platform_admin(true) ||
2140
                api_is_drh() ||
2141
                api_is_coach($sessionId, $courseId, false)
2142
            ) {
2143
                return false;
2144
            }
2145
        }
2146
2147
        $isBlocked = false;
2148
        if (!empty($prerequisite)) {
2149
            $progress = self::getProgress(
2150
                $prerequisite,
2151
                $studentId,
2152
                $courseId,
2153
                $sessionId
2154
            );
2155
            if ($progress < 100) {
2156
                $isBlocked = true;
2157
            }
2158
2159
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2160
                // Block if it does not exceed minimum time
2161
                // Minimum time (in minutes) to pass the learning path
2162
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
2163
2164
                if ($accumulateWorkTime > 0) {
2165
                    // Total time in course (sum of times in learning paths from course)
2166
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
2167
2168
                    // Connect with the plugin_licences_course_session table
2169
                    // which indicates what percentage of the time applies
2170
                    // Minimum connection percentage
2171
                    $perc = 100;
2172
                    // Time from the course
2173
                    $tc = $accumulateWorkTimeTotal;
2174
2175
                    // Percentage of the learning paths
2176
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
2177
                    // Minimum time for each learning path
2178
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
2179
2180
                    // Spent time (in seconds) so far in the learning path
2181
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
2182
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
2183
2184
                    if ($lpTime < ($accumulateWorkTime * 60)) {
2185
                        $isBlocked = true;
2186
                    }
2187
                }
2188
            }
2189
        }
2190
2191
        return $isBlocked;
2192
    }
2193
2194
    /**
2195
     * Checks if the learning path is visible for student after the progress
2196
     * of its prerequisite is completed, considering the time availability and
2197
     * the LP visibility.
2198
     *
2199
     * @param int   $student_id
2200
     * @param array $courseInfo
2201
     * @param int   $sessionId
2202
     *
2203
     * @return bool
2204
     */
2205
    public static function is_lp_visible_for_student(
2206
        CLp $lp,
2207
        $student_id,
2208
        $courseInfo = [],
2209
        $sessionId = 0
2210
    ) {
2211
        $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
2212
        $sessionId = (int) $sessionId;
2213
2214
        if (empty($courseInfo)) {
2215
            return false;
2216
        }
2217
2218
        if (empty($sessionId)) {
2219
            $sessionId = api_get_session_id();
2220
        }
2221
2222
        $courseId = $courseInfo['real_id'];
2223
2224
        /*$itemInfo = api_get_item_property_info(
2225
            $courseId,
2226
            TOOL_LEARNPATH,
2227
            $lp_id,
2228
            $sessionId
2229
        );*/
2230
2231
        $visibility = $lp->isVisible($courseInfo['entity'], api_get_session_entity($sessionId));
2232
        // If the item was deleted.
2233
        if (false === $visibility) {
2234
            return false;
2235
        }
2236
2237
        $lp_id = $lp->getIid();
2238
        // @todo remove this query and load the row info as a parameter
2239
        $table = Database::get_course_table(TABLE_LP_MAIN);
2240
        // Get current prerequisite
2241
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on, category_id
2242
                FROM $table
2243
                WHERE iid = $lp_id";
2244
        $rs = Database::query($sql);
2245
        $now = time();
2246
        if (Database::num_rows($rs) > 0) {
2247
            $row = Database::fetch_array($rs, 'ASSOC');
2248
2249
            if (!empty($row['category_id'])) {
2250
                $em = Database::getManager();
2251
                $category = $em->getRepository('ChamiloCourseBundle:CLpCategory')->find($row['category_id']);
2252
                if (false === self::categoryIsVisibleForStudent($category, api_get_user_entity($student_id))) {
2253
                    return false;
2254
                }
2255
            }
2256
2257
            $prerequisite = $row['prerequisite'];
2258
            $is_visible = true;
2259
2260
            $isBlocked = self::isBlockedByPrerequisite(
2261
                $student_id,
2262
                $prerequisite,
2263
                $courseInfo,
2264
                $sessionId
2265
            );
2266
2267
            if ($isBlocked) {
2268
                $is_visible = false;
2269
            }
2270
2271
            // Also check the time availability of the LP
2272
            if ($is_visible) {
2273
                // Adding visibility restrictions
2274
                if (!empty($row['publicated_on'])) {
2275
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2276
                        $is_visible = false;
2277
                    }
2278
                }
2279
                // Blocking empty start times see BT#2800
2280
                global $_custom;
2281
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2282
                    $_custom['lps_hidden_when_no_start_date']
2283
                ) {
2284
                    if (empty($row['publicated_on'])) {
2285
                        $is_visible = false;
2286
                    }
2287
                }
2288
2289
                if (!empty($row['expired_on'])) {
2290
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2291
                        $is_visible = false;
2292
                    }
2293
                }
2294
            }
2295
2296
            if ($is_visible) {
2297
                $subscriptionSettings = self::getSubscriptionSettings();
2298
2299
                // Check if the subscription users/group to a LP is ON
2300
                if (isset($row['subscribe_users']) && 1 == $row['subscribe_users'] &&
2301
                    true === $subscriptionSettings['allow_add_users_to_lp']
2302
                ) {
2303
                    // Try group
2304
                    $is_visible = false;
2305
                    // Checking only the user visibility
2306
                    $userVisibility = api_get_item_visibility(
2307
                        $courseInfo,
2308
                        'learnpath',
2309
                        $row['id'],
2310
                        $sessionId,
2311
                        $student_id,
2312
                        'LearnpathSubscription'
2313
                    );
2314
2315
                    if (1 == $userVisibility) {
2316
                        $is_visible = true;
2317
                    } else {
2318
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
2319
                        if (!empty($userGroups)) {
2320
                            foreach ($userGroups as $groupInfo) {
2321
                                $groupId = $groupInfo['iid'];
2322
                                $userVisibility = api_get_item_visibility(
2323
                                    $courseInfo,
2324
                                    'learnpath',
2325
                                    $row['id'],
2326
                                    $sessionId,
2327
                                    null,
2328
                                    'LearnpathSubscription',
2329
                                    $groupId
2330
                                );
2331
2332
                                if (1 == $userVisibility) {
2333
                                    $is_visible = true;
2334
                                    break;
2335
                                }
2336
                            }
2337
                        }
2338
                    }
2339
                }
2340
            }
2341
2342
            return $is_visible;
2343
        }
2344
2345
        return false;
2346
    }
2347
2348
    /**
2349
     * @param int $lpId
2350
     * @param int $userId
2351
     * @param int $courseId
2352
     * @param int $sessionId
2353
     *
2354
     * @return int
2355
     */
2356
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2357
    {
2358
        $lpId = (int) $lpId;
2359
        $userId = (int) $userId;
2360
        $courseId = (int) $courseId;
2361
        $sessionId = (int) $sessionId;
2362
2363
        $sessionCondition = api_get_session_condition($sessionId);
2364
        $table = Database::get_course_table(TABLE_LP_VIEW);
2365
        $sql = "SELECT progress FROM $table
2366
                WHERE
2367
                    c_id = $courseId AND
2368
                    lp_id = $lpId AND
2369
                    user_id = $userId $sessionCondition ";
2370
        $res = Database::query($sql);
2371
2372
        $progress = 0;
2373
        if (Database::num_rows($res) > 0) {
2374
            $row = Database::fetch_array($res);
2375
            $progress = (int) $row['progress'];
2376
        }
2377
2378
        return $progress;
2379
    }
2380
2381
    /**
2382
     * @param array $lpList
2383
     * @param int   $userId
2384
     * @param int   $courseId
2385
     * @param int   $sessionId
2386
     *
2387
     * @return array
2388
     */
2389
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2390
    {
2391
        $lpList = array_map('intval', $lpList);
2392
        if (empty($lpList)) {
2393
            return [];
2394
        }
2395
2396
        $lpList = implode("','", $lpList);
2397
2398
        $userId = (int) $userId;
2399
        $courseId = (int) $courseId;
2400
        $sessionId = (int) $sessionId;
2401
2402
        $sessionCondition = api_get_session_condition($sessionId);
2403
        $table = Database::get_course_table(TABLE_LP_VIEW);
2404
        $sql = "SELECT lp_id, progress FROM $table
2405
                WHERE
2406
                    c_id = $courseId AND
2407
                    lp_id IN ('".$lpList."') AND
2408
                    user_id = $userId $sessionCondition ";
2409
        $res = Database::query($sql);
2410
2411
        if (Database::num_rows($res) > 0) {
2412
            $list = [];
2413
            while ($row = Database::fetch_array($res)) {
2414
                $list[$row['lp_id']] = $row['progress'];
2415
            }
2416
2417
            return $list;
2418
        }
2419
2420
        return [];
2421
    }
2422
2423
    /**
2424
     * Displays a progress bar
2425
     * completed so far.
2426
     *
2427
     * @param int    $percentage Progress value to display
2428
     * @param string $text_add   Text to display near the progress value
2429
     *
2430
     * @return string HTML string containing the progress bar
2431
     */
2432
    public static function get_progress_bar($percentage = -1, $text_add = '')
2433
    {
2434
        $text = $percentage.$text_add;
2435
        $output = '<div class="progress">
2436
            <div id="progress_bar_value"
2437
                class="progress-bar progress-bar-success" role="progressbar"
2438
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2439
            '.$text.'
2440
            </div>
2441
        </div>';
2442
2443
        return $output;
2444
    }
2445
2446
    /**
2447
     * @param string $mode can be '%' or 'abs'
2448
     *                     otherwise this value will be used $this->progress_bar_mode
2449
     *
2450
     * @return string
2451
     */
2452
    public function getProgressBar($mode = null)
2453
    {
2454
        [$percentage, $text_add] = $this->get_progress_bar_text($mode);
2455
2456
        return self::get_progress_bar($percentage, $text_add);
2457
    }
2458
2459
    /**
2460
     * Gets the progress bar info to display inside the progress bar.
2461
     * Also used by scorm_api.php.
2462
     *
2463
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2464
     *                     we display a number of completed elements per total elements
2465
     * @param int    $add  Additional steps to fake as completed
2466
     *
2467
     * @return array Percentage or number and symbol (% or /xx)
2468
     */
2469
    public function get_progress_bar_text($mode = '', $add = 0)
2470
    {
2471
        if (empty($mode)) {
2472
            $mode = $this->progress_bar_mode;
2473
        }
2474
        $text = '';
2475
        $percentage = 0;
2476
        // If the option to use the score as progress is set for this learning
2477
        // path, then the rules are completely different: we assume only one
2478
        // item exists and the progress of the LP depends on the score
2479
        $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
2480
        if (true === $scoreAsProgressSetting) {
2481
            $scoreAsProgress = $this->getUseScoreAsProgress();
2482
            if ($scoreAsProgress) {
2483
                // Get single item's score
2484
                $itemId = $this->get_current_item_id();
2485
                $item = $this->getItem($itemId);
2486
                $score = $item->get_score();
2487
                $maxScore = $item->get_max();
2488
                if ($mode = '%') {
2489
                    if (!empty($maxScore)) {
2490
                        $percentage = ((float) $score / (float) $maxScore) * 100;
2491
                    }
2492
                    $percentage = number_format($percentage, 0);
2493
                    $text = '%';
2494
                } else {
2495
                    $percentage = $score;
2496
                    $text = '/'.$maxScore;
2497
                }
2498
2499
                return [$percentage, $text];
2500
            }
2501
        }
2502
        // otherwise just continue the normal processing of progress
2503
        $total_items = $this->getTotalItemsCountWithoutDirs();
2504
        $completeItems = $this->get_complete_items_count();
2505
        if (0 != $add) {
2506
            $completeItems += $add;
2507
        }
2508
        if ($completeItems > $total_items) {
2509
            $completeItems = $total_items;
2510
        }
2511
        if ('%' == $mode) {
2512
            if ($total_items > 0) {
2513
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2514
            }
2515
            $percentage = number_format($percentage, 0);
2516
            $text = '%';
2517
        } elseif ('abs' === $mode) {
2518
            $percentage = $completeItems;
2519
            $text = '/'.$total_items;
2520
        }
2521
2522
        return [
2523
            $percentage,
2524
            $text,
2525
        ];
2526
    }
2527
2528
    /**
2529
     * Gets the progress bar mode.
2530
     *
2531
     * @return string The progress bar mode attribute
2532
     */
2533
    public function get_progress_bar_mode()
2534
    {
2535
        if (!empty($this->progress_bar_mode)) {
2536
            return $this->progress_bar_mode;
2537
        }
2538
2539
        return '%';
2540
    }
2541
2542
    /**
2543
     * Gets the learnpath theme (remote or local).
2544
     *
2545
     * @return string Learnpath theme
2546
     */
2547
    public function get_theme()
2548
    {
2549
        if (!empty($this->theme)) {
2550
            return $this->theme;
2551
        }
2552
2553
        return '';
2554
    }
2555
2556
    /**
2557
     * Gets the learnpath session id.
2558
     *
2559
     * @return int
2560
     */
2561
    public function get_lp_session_id()
2562
    {
2563
        if (!empty($this->lp_session_id)) {
2564
            return (int) $this->lp_session_id;
2565
        }
2566
2567
        return 0;
2568
    }
2569
2570
    /**
2571
     * Gets the learnpath author.
2572
     *
2573
     * @return string LP's author
2574
     */
2575
    public function get_author()
2576
    {
2577
        if (!empty($this->author)) {
2578
            return $this->author;
2579
        }
2580
2581
        return '';
2582
    }
2583
2584
    /**
2585
     * Gets hide table of contents.
2586
     *
2587
     * @return int
2588
     */
2589
    public function getHideTableOfContents()
2590
    {
2591
        return (int) $this->hide_toc_frame;
2592
    }
2593
2594
    /**
2595
     * Generate a new prerequisites string for a given item. If this item was a sco and
2596
     * its prerequisites were strings (instead of IDs), then transform those strings into
2597
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2598
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2599
     * same rule as the scormExport() method.
2600
     *
2601
     * @param int $item_id Item ID
2602
     *
2603
     * @return string Prerequisites string ready for the export as SCORM
2604
     */
2605
    public function get_scorm_prereq_string($item_id)
2606
    {
2607
        if ($this->debug > 0) {
2608
            error_log('In learnpath::get_scorm_prereq_string()');
2609
        }
2610
        if (!is_object($this->items[$item_id])) {
2611
            return false;
2612
        }
2613
        /** @var learnpathItem $oItem */
2614
        $oItem = $this->items[$item_id];
2615
        $prereq = $oItem->get_prereq_string();
2616
2617
        if (empty($prereq)) {
2618
            return '';
2619
        }
2620
        if (preg_match('/^\d+$/', $prereq) &&
2621
            isset($this->items[$prereq]) &&
2622
            is_object($this->items[$prereq])
2623
        ) {
2624
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2625
            // then simply return it (with the ITEM_ prefix).
2626
            //return 'ITEM_' . $prereq;
2627
            return $this->items[$prereq]->ref;
2628
        } else {
2629
            if (isset($this->refs_list[$prereq])) {
2630
                // It's a simple string item from which the ID can be found in the refs list,
2631
                // so we can transform it directly to an ID for export.
2632
                return $this->items[$this->refs_list[$prereq]]->ref;
2633
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2634
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2635
            } else {
2636
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2637
                // and replace them, one by one, by the internal IDs (chamilo db)
2638
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2639
                // by a space as well.
2640
                $find = [
2641
                    '&',
2642
                    '|',
2643
                    '~',
2644
                    '=',
2645
                    '<>',
2646
                    '{',
2647
                    '}',
2648
                    '*',
2649
                    '(',
2650
                    ')',
2651
                ];
2652
                $replace = [
2653
                    ' ',
2654
                    ' ',
2655
                    ' ',
2656
                    ' ',
2657
                    ' ',
2658
                    ' ',
2659
                    ' ',
2660
                    ' ',
2661
                    ' ',
2662
                    ' ',
2663
                ];
2664
                $prereq_mod = str_replace($find, $replace, $prereq);
2665
                $ids = explode(' ', $prereq_mod);
2666
                foreach ($ids as $id) {
2667
                    $id = trim($id);
2668
                    if (isset($this->refs_list[$id])) {
2669
                        $prereq = preg_replace(
2670
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2671
                            'ITEM_'.$this->refs_list[$id],
2672
                            $prereq
2673
                        );
2674
                    }
2675
                }
2676
2677
                return $prereq;
2678
            }
2679
        }
2680
    }
2681
2682
    /**
2683
     * Returns the XML DOM document's node.
2684
     *
2685
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2686
     * @param string   $id       The identifier to look for
2687
     *
2688
     * @return mixed The reference to the element found with that identifier. False if not found
2689
     */
2690
    public function get_scorm_xml_node(&$children, $id)
2691
    {
2692
        for ($i = 0; $i < $children->length; $i++) {
2693
            $item_temp = $children->item($i);
2694
            if ('item' == $item_temp->nodeName) {
2695
                if ($item_temp->getAttribute('identifier') == $id) {
2696
                    return $item_temp;
2697
                }
2698
            }
2699
            $subchildren = $item_temp->childNodes;
2700
            if ($subchildren && $subchildren->length > 0) {
2701
                $val = $this->get_scorm_xml_node($subchildren, $id);
2702
                if (is_object($val)) {
2703
                    return $val;
2704
                }
2705
            }
2706
        }
2707
2708
        return false;
2709
    }
2710
2711
    /**
2712
     * Gets the status list for all LP's items.
2713
     *
2714
     * @return array Array of [index] => [item ID => current status]
2715
     */
2716
    public function get_items_status_list()
2717
    {
2718
        $list = [];
2719
        foreach ($this->ordered_items as $item_id) {
2720
            $list[] = [
2721
                $item_id => $this->items[$item_id]->get_status(),
2722
            ];
2723
        }
2724
2725
        return $list;
2726
    }
2727
2728
    /**
2729
     * Return the number of interactions for the given learnpath Item View ID.
2730
     * This method can be used as static.
2731
     *
2732
     * @param int $lp_iv_id  Item View ID
2733
     * @param int $course_id course id
2734
     *
2735
     * @return int
2736
     */
2737
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2738
    {
2739
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2740
        $lp_iv_id = (int) $lp_iv_id;
2741
        $course_id = (int) $course_id;
2742
2743
        $sql = "SELECT count(*) FROM $table
2744
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2745
        $res = Database::query($sql);
2746
        $num = 0;
2747
        if (Database::num_rows($res)) {
2748
            $row = Database::fetch_array($res);
2749
            $num = $row[0];
2750
        }
2751
2752
        return $num;
2753
    }
2754
2755
    /**
2756
     * Return the interactions as an array for the given lp_iv_id.
2757
     * This method can be used as static.
2758
     *
2759
     * @param int $lp_iv_id Learnpath Item View ID
2760
     *
2761
     * @return array
2762
     *
2763
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2764
     */
2765
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2766
    {
2767
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2768
        $list = [];
2769
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2770
        $lp_iv_id = (int) $lp_iv_id;
2771
2772
        if (empty($lp_iv_id) || empty($course_id)) {
2773
            return [];
2774
        }
2775
2776
        $sql = "SELECT * FROM $table
2777
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2778
                ORDER BY order_id ASC";
2779
        $res = Database::query($sql);
2780
        $num = Database::num_rows($res);
2781
        if ($num > 0) {
2782
            $list[] = [
2783
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2784
                'id' => api_htmlentities(get_lang('Interaction ID'), ENT_QUOTES),
2785
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2786
                'time' => api_htmlentities(get_lang('Time (finished at...)'), ENT_QUOTES),
2787
                'correct_responses' => api_htmlentities(get_lang('Correct answers'), ENT_QUOTES),
2788
                'student_response' => api_htmlentities(get_lang('Learner answers'), ENT_QUOTES),
2789
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2790
                'latency' => api_htmlentities(get_lang('Time spent'), ENT_QUOTES),
2791
                'student_response_formatted' => '',
2792
            ];
2793
            while ($row = Database::fetch_array($res)) {
2794
                $studentResponseFormatted = urldecode($row['student_response']);
2795
                $content_student_response = explode('__|', $studentResponseFormatted);
2796
                if (count($content_student_response) > 0) {
2797
                    if (count($content_student_response) >= 3) {
2798
                        // Pop the element off the end of array.
2799
                        array_pop($content_student_response);
2800
                    }
2801
                    $studentResponseFormatted = implode(',', $content_student_response);
2802
                }
2803
2804
                $list[] = [
2805
                    'order_id' => $row['order_id'] + 1,
2806
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2807
                    'type' => $row['interaction_type'],
2808
                    'time' => $row['completion_time'],
2809
                    'correct_responses' => '', // Hide correct responses from students.
2810
                    'student_response' => $row['student_response'],
2811
                    'result' => $row['result'],
2812
                    'latency' => $row['latency'],
2813
                    'student_response_formatted' => $studentResponseFormatted,
2814
                ];
2815
            }
2816
        }
2817
2818
        return $list;
2819
    }
2820
2821
    /**
2822
     * Return the number of objectives for the given learnpath Item View ID.
2823
     * This method can be used as static.
2824
     *
2825
     * @param int $lp_iv_id  Item View ID
2826
     * @param int $course_id Course ID
2827
     *
2828
     * @return int Number of objectives
2829
     */
2830
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2831
    {
2832
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2833
        $course_id = (int) $course_id;
2834
        $lp_iv_id = (int) $lp_iv_id;
2835
        $sql = "SELECT count(*) FROM $table
2836
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2837
        //@todo seems that this always returns 0
2838
        $res = Database::query($sql);
2839
        $num = 0;
2840
        if (Database::num_rows($res)) {
2841
            $row = Database::fetch_array($res);
2842
            $num = $row[0];
2843
        }
2844
2845
        return $num;
2846
    }
2847
2848
    /**
2849
     * Return the objectives as an array for the given lp_iv_id.
2850
     * This method can be used as static.
2851
     *
2852
     * @param int $lpItemViewId Learnpath Item View ID
2853
     * @param int $course_id
2854
     *
2855
     * @return array
2856
     *
2857
     * @todo    Translate labels
2858
     */
2859
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
2860
    {
2861
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2862
        $lpItemViewId = (int) $lpItemViewId;
2863
2864
        if (empty($course_id) || empty($lpItemViewId)) {
2865
            return [];
2866
        }
2867
2868
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2869
        $sql = "SELECT * FROM $table
2870
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
2871
                ORDER BY order_id ASC";
2872
        $res = Database::query($sql);
2873
        $num = Database::num_rows($res);
2874
        $list = [];
2875
        if ($num > 0) {
2876
            $list[] = [
2877
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2878
                'objective_id' => api_htmlentities(get_lang('Objective ID'), ENT_QUOTES),
2879
                'score_raw' => api_htmlentities(get_lang('Objective raw score'), ENT_QUOTES),
2880
                'score_max' => api_htmlentities(get_lang('Objective max score'), ENT_QUOTES),
2881
                'score_min' => api_htmlentities(get_lang('Objective min score'), ENT_QUOTES),
2882
                'status' => api_htmlentities(get_lang('Objective status'), ENT_QUOTES),
2883
            ];
2884
            while ($row = Database::fetch_array($res)) {
2885
                $list[] = [
2886
                    'order_id' => $row['order_id'] + 1,
2887
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
2888
                    'score_raw' => $row['score_raw'],
2889
                    'score_max' => $row['score_max'],
2890
                    'score_min' => $row['score_min'],
2891
                    'status' => $row['status'],
2892
                ];
2893
            }
2894
        }
2895
2896
        return $list;
2897
    }
2898
2899
    /**
2900
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
2901
     * used by get_html_toc() to be ready to display.
2902
     *
2903
     * @return array TOC as a table with 4 elements per row: title, link, status and level
2904
     */
2905
    public function get_toc()
2906
    {
2907
        $toc = [];
2908
        foreach ($this->ordered_items as $item_id) {
2909
            // TODO: Change this link generation and use new function instead.
2910
            $toc[] = [
2911
                'id' => $item_id,
2912
                'title' => $this->items[$item_id]->get_title(),
2913
                'status' => $this->items[$item_id]->get_status(),
2914
                'level' => $this->items[$item_id]->get_level(),
2915
                'type' => $this->items[$item_id]->get_type(),
2916
                'description' => $this->items[$item_id]->get_description(),
2917
                'path' => $this->items[$item_id]->get_path(),
2918
                'parent' => $this->items[$item_id]->get_parent(),
2919
            ];
2920
        }
2921
2922
        return $toc;
2923
    }
2924
2925
    /**
2926
     * Returns the CSS class name associated with a given item status.
2927
     *
2928
     * @param $status string an item status
2929
     *
2930
     * @return string CSS class name
2931
     */
2932
    public static function getStatusCSSClassName($status)
2933
    {
2934
        if (array_key_exists($status, self::STATUS_CSS_CLASS_NAME)) {
2935
            return self::STATUS_CSS_CLASS_NAME[$status];
2936
        }
2937
2938
        return '';
2939
    }
2940
2941
    /**
2942
     * Generate the tree of contents for this learnpath as an associative array tree
2943
     * with keys id, title, status, type, description, path, parent_id, children
2944
     * (title and descriptions as secured)
2945
     * and clues for CSS class composition:
2946
     *  - booleans is_current, is_parent_of_current, is_chapter
2947
     *  - string status_css_class_name.
2948
     *
2949
     * @param $parentId int restrict returned list to children of this parent
2950
     *
2951
     * @return array TOC as a table
2952
     */
2953
    public function getTOCTree($parentId = 0)
2954
    {
2955
        $toc = [];
2956
        $currentItemId = $this->get_current_item_id();
2957
2958
        foreach ($this->ordered_items as $itemId) {
2959
            $item = $this->items[$itemId];
2960
            if ($item->get_parent() == $parentId) {
2961
                $title = $item->get_title();
2962
                if (empty($title)) {
2963
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $itemId);
2964
                }
2965
2966
                $itemData = [
2967
                    'id' => $itemId,
2968
                    'title' => Security::remove_XSS($title),
2969
                    'status' => $item->get_status(),
2970
                    'level' => $item->get_level(), // FIXME should not be needed
2971
                    'type' => $item->get_type(),
2972
                    'description' => Security::remove_XSS($item->get_description()),
2973
                    'path' => $item->get_path(),
2974
                    'parent_id' => $item->get_parent(),
2975
                    'children' => $this->getTOCTree($itemId),
2976
                    'is_current' => ($itemId == $currentItemId),
2977
                    'is_parent_of_current' => false,
2978
                    'is_chapter' => in_array($item->get_type(), self::getChapterTypes()),
2979
                    'status_css_class_name' => $this->getStatusCSSClassName($item->get_status()),
2980
                    'current_id' => $currentItemId, // FIXME should not be needed, not a property of item
2981
                ];
2982
2983
                if (!empty($itemData['children'])) {
2984
                    foreach ($itemData['children'] as $child) {
2985
                        if ($child['is_current'] || $child['is_parent_of_current']) {
2986
                            $itemData['is_parent_of_current'] = true;
2987
                            break;
2988
                        }
2989
                    }
2990
                }
2991
2992
                $toc[] = $itemData;
2993
            }
2994
        }
2995
2996
        return $toc;
2997
    }
2998
2999
    /**
3000
     * Generate and return the table of contents for this learnpath. The JS
3001
     * table returned is used inside of scorm_api.php.
3002
     *
3003
     * @param string $varname
3004
     *
3005
     * @return string A JS array variable construction
3006
     */
3007
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
3008
    {
3009
        $toc = $varname.' = new Array();';
3010
        foreach ($this->ordered_items as $item_id) {
3011
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
3012
        }
3013
3014
        return $toc;
3015
    }
3016
3017
    /**
3018
     * Gets the learning path type.
3019
     *
3020
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
3021
     *
3022
     * @return mixed Type ID or name, depending on the parameter
3023
     */
3024
    public function get_type($get_name = false)
3025
    {
3026
        $res = false;
3027
        if (!empty($this->type) && (!$get_name)) {
3028
            $res = $this->type;
3029
        }
3030
3031
        return $res;
3032
    }
3033
3034
    /**
3035
     * Gets the learning path type as static method.
3036
     *
3037
     * @param int $lp_id
3038
     *
3039
     * @return mixed Type ID or name, depending on the parameter
3040
     */
3041
    public static function get_type_static($lp_id = 0)
3042
    {
3043
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3044
        $lp_id = (int) $lp_id;
3045
        $sql = "SELECT lp_type FROM $tbl_lp
3046
                WHERE iid = $lp_id";
3047
        $res = Database::query($sql);
3048
        if (false === $res) {
3049
            return null;
3050
        }
3051
        if (Database::num_rows($res) <= 0) {
3052
            return null;
3053
        }
3054
        $row = Database::fetch_array($res);
3055
3056
        return $row['lp_type'];
3057
    }
3058
3059
    /**
3060
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3061
     * This method can be used as abstract and is recursive.
3062
     *
3063
     * @param int $lp        Learnpath ID
3064
     * @param int $parent    Parent ID of the items to look for
3065
     * @param int $course_id
3066
     *
3067
     * @return array Ordered list of item IDs (empty array on error)
3068
     */
3069
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3070
    {
3071
        if (empty($course_id)) {
3072
            $course_id = api_get_course_int_id();
3073
        } else {
3074
            $course_id = (int) $course_id;
3075
        }
3076
        $list = [];
3077
3078
        if (empty($lp)) {
3079
            return $list;
3080
        }
3081
3082
        $lp = (int) $lp;
3083
        $parent = (int) $parent;
3084
3085
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3086
        $sql = "SELECT iid FROM $tbl_lp_item
3087
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3088
                ORDER BY display_order";
3089
3090
        $res = Database::query($sql);
3091
        while ($row = Database::fetch_array($res)) {
3092
            $sublist = self::get_flat_ordered_items_list(
3093
                $lp,
3094
                $row['iid'],
3095
                $course_id
3096
            );
3097
            $list[] = $row['iid'];
3098
            foreach ($sublist as $item) {
3099
                $list[] = $item;
3100
            }
3101
        }
3102
3103
        return $list;
3104
    }
3105
3106
    /**
3107
     * @return array
3108
     */
3109
    public static function getChapterTypes()
3110
    {
3111
        return [
3112
            'dir',
3113
        ];
3114
    }
3115
3116
    /**
3117
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3118
     *
3119
     * @param $tree
3120
     *
3121
     * @return array HTML TOC ready to display
3122
     */
3123
    public function getParentToc($tree)
3124
    {
3125
        if (empty($tree)) {
3126
            $tree = $this->get_toc();
3127
        }
3128
        $dirTypes = self::getChapterTypes();
3129
        $myCurrentId = $this->get_current_item_id();
3130
        $listParent = [];
3131
        $listChildren = [];
3132
        $listNotParent = [];
3133
        $list = [];
3134
        foreach ($tree as $subtree) {
3135
            if (in_array($subtree['type'], $dirTypes)) {
3136
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3137
                $subtree['children'] = $listChildren;
3138
                if (!empty($subtree['children'])) {
3139
                    foreach ($subtree['children'] as $subItem) {
3140
                        if ($subItem['id'] == $this->current) {
3141
                            $subtree['parent_current'] = 'in';
3142
                            $subtree['current'] = 'on';
3143
                        }
3144
                    }
3145
                }
3146
                $listParent[] = $subtree;
3147
            }
3148
            if (!in_array($subtree['type'], $dirTypes) && null == $subtree['parent']) {
3149
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3150
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3151
                }
3152
3153
                $title = Security::remove_XSS($subtree['title']);
3154
                unset($subtree['title']);
3155
3156
                if (empty($title)) {
3157
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3158
                }
3159
                $classStyle = null;
3160
                if ($subtree['id'] == $this->current) {
3161
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3162
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3163
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3164
                }
3165
                $subtree['title'] = $title;
3166
                $subtree['class'] = $classStyle.' '.$cssStatus;
3167
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3168
                $subtree['current_id'] = $myCurrentId;
3169
                $listNotParent[] = $subtree;
3170
            }
3171
        }
3172
3173
        $list['are_parents'] = $listParent;
3174
        $list['not_parents'] = $listNotParent;
3175
3176
        return $list;
3177
    }
3178
3179
    /**
3180
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3181
     *
3182
     * @param array $tree
3183
     * @param int   $id
3184
     * @param bool  $parent
3185
     *
3186
     * @return array HTML TOC ready to display
3187
     */
3188
    public function getChildrenToc($tree, $id, $parent = true)
3189
    {
3190
        if (empty($tree)) {
3191
            $tree = $this->get_toc();
3192
        }
3193
3194
        $dirTypes = self::getChapterTypes();
3195
        $currentItemId = $this->get_current_item_id();
3196
        $list = [];
3197
3198
        foreach ($tree as $subtree) {
3199
            $subtree['tree'] = null;
3200
3201
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3202
                if ($subtree['id'] == $this->current) {
3203
                    $subtree['current'] = 'active';
3204
                } else {
3205
                    $subtree['current'] = null;
3206
                }
3207
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3208
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3209
                }
3210
3211
                $title = Security::remove_XSS($subtree['title']);
3212
                unset($subtree['title']);
3213
                if (empty($title)) {
3214
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3215
                }
3216
3217
                $classStyle = null;
3218
                if ($subtree['id'] == $this->current) {
3219
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3220
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3221
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3222
                }
3223
3224
                if (in_array($subtree['type'], $dirTypes)) {
3225
                    $subtree['title'] = stripslashes($title);
3226
                } else {
3227
                    $subtree['title'] = $title;
3228
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3229
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3230
                    $subtree['current_id'] = $currentItemId;
3231
                }
3232
                $list[] = $subtree;
3233
            }
3234
        }
3235
3236
        return $list;
3237
    }
3238
3239
    /**
3240
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3241
     *
3242
     * @param array $toc_list
3243
     *
3244
     * @return array HTML TOC ready to display
3245
     */
3246
    public function getListArrayToc($toc_list = [])
3247
    {
3248
        if (empty($toc_list)) {
3249
            $toc_list = $this->get_toc();
3250
        }
3251
        // Temporary variables.
3252
        $currentItemId = $this->get_current_item_id();
3253
        $list = [];
3254
        $arrayList = [];
3255
3256
        foreach ($toc_list as $item) {
3257
            $list['id'] = $item['id'];
3258
            $list['status'] = $item['status'];
3259
            $cssStatus = null;
3260
3261
            if (array_key_exists($item['status'], self::STATUS_CSS_CLASS_NAME)) {
3262
                $cssStatus = self::STATUS_CSS_CLASS_NAME[$item['status']];
3263
            }
3264
3265
            $classStyle = ' ';
3266
            $dirTypes = self::getChapterTypes();
3267
3268
            if (in_array($item['type'], $dirTypes)) {
3269
                $classStyle = 'scorm_item_section ';
3270
            }
3271
            if ($item['id'] == $this->current) {
3272
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3273
            } elseif (!in_array($item['type'], $dirTypes)) {
3274
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3275
            }
3276
            $title = $item['title'];
3277
            if (empty($title)) {
3278
                $title = self::rl_get_resource_name(
3279
                    api_get_course_id(),
3280
                    $this->get_id(),
3281
                    $item['id']
3282
                );
3283
            }
3284
            $title = Security::remove_XSS($item['title']);
3285
3286
            if (empty($item['description'])) {
3287
                $list['description'] = $title;
3288
            } else {
3289
                $list['description'] = $item['description'];
3290
            }
3291
3292
            $list['class'] = $classStyle.' '.$cssStatus;
3293
            $list['level'] = $item['level'];
3294
            $list['type'] = $item['type'];
3295
3296
            if (in_array($item['type'], $dirTypes)) {
3297
                $list['css_level'] = 'level_'.$item['level'];
3298
            } else {
3299
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3300
            }
3301
3302
            if (in_array($item['type'], $dirTypes)) {
3303
                $list['title'] = stripslashes($title);
3304
            } else {
3305
                $list['title'] = stripslashes($title);
3306
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3307
                $list['current_id'] = $currentItemId;
3308
            }
3309
            $arrayList[] = $list;
3310
        }
3311
3312
        return $arrayList;
3313
    }
3314
3315
    /**
3316
     * Returns an HTML-formatted string ready to display with teacher buttons
3317
     * in LP view menu.
3318
     *
3319
     * @return string HTML TOC ready to display
3320
     */
3321
    public function get_teacher_toc_buttons()
3322
    {
3323
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3324
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3325
        $html = '';
3326
        if ($isAllow && false == $hideIcons) {
3327
            if ($this->get_lp_session_id() == api_get_session_id()) {
3328
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3329
                $html .= '<div class="btn-group">';
3330
                $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'>".
3331
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3332
                $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'>".
3333
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3334
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3335
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3336
                $html .= '</div>';
3337
                $html .= '</div>';
3338
            }
3339
        }
3340
3341
        return $html;
3342
    }
3343
3344
    /**
3345
     * Gets the learnpath maker name - generally the editor's name.
3346
     *
3347
     * @return string Learnpath maker name
3348
     */
3349
    public function get_maker()
3350
    {
3351
        if (!empty($this->maker)) {
3352
            return $this->maker;
3353
        }
3354
3355
        return '';
3356
    }
3357
3358
    /**
3359
     * Gets the learnpath name/title.
3360
     *
3361
     * @return string Learnpath name/title
3362
     */
3363
    public function get_name()
3364
    {
3365
        if (!empty($this->name)) {
3366
            return $this->name;
3367
        }
3368
3369
        return 'N/A';
3370
    }
3371
3372
    /**
3373
     * @return string
3374
     */
3375
    public function getNameNoTags()
3376
    {
3377
        return strip_tags($this->get_name());
3378
    }
3379
3380
    /**
3381
     * Gets a link to the resource from the present location, depending on item ID.
3382
     *
3383
     * @param string $type         Type of link expected
3384
     * @param int    $item_id      Learnpath item ID
3385
     * @param bool   $provided_toc
3386
     *
3387
     * @return string $provided_toc Link to the lp_item resource
3388
     */
3389
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
3390
    {
3391
        $course_id = $this->get_course_int_id();
3392
        $item_id = (int) $item_id;
3393
3394
        if (empty($item_id)) {
3395
            $item_id = $this->get_current_item_id();
3396
3397
            if (empty($item_id)) {
3398
                //still empty, this means there was no item_id given and we are not in an object context or
3399
                //the object property is empty, return empty link
3400
                $this->first();
3401
3402
                return '';
3403
            }
3404
        }
3405
3406
        $file = '';
3407
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3408
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3409
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3410
3411
        $sql = "SELECT
3412
                    l.lp_type as ltype,
3413
                    l.path as lpath,
3414
                    li.item_type as litype,
3415
                    li.path as lipath,
3416
                    li.parameters as liparams
3417
        		FROM $lp_table l
3418
                INNER JOIN $lp_item_table li
3419
                ON (li.lp_id = l.iid)
3420
        		WHERE
3421
        		    li.iid = $item_id
3422
        		";
3423
        $res = Database::query($sql);
3424
        if (Database::num_rows($res) > 0) {
3425
            $row = Database::fetch_array($res);
3426
            $lp_type = $row['ltype'];
3427
            $lp_path = $row['lpath'];
3428
            $lp_item_type = $row['litype'];
3429
            $lp_item_path = $row['lipath'];
3430
            $lp_item_params = $row['liparams'];
3431
3432
            if (empty($lp_item_params) && false !== strpos($lp_item_path, '?')) {
3433
                [$lp_item_path, $lp_item_params] = explode('?', $lp_item_path);
3434
            }
3435
            //$sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3436
            if ('http' === $type) {
3437
                //web path
3438
                //$course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3439
            } else {
3440
                //$course_path = $sys_course_path; //system path
3441
            }
3442
3443
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3444
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3445
            if (in_array(
3446
                $lp_item_type,
3447
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3448
            )
3449
            ) {
3450
                $lp_type = 1;
3451
            }
3452
3453
            // Now go through the specific cases to get the end of the path
3454
            // @todo Use constants instead of int values.
3455
            switch ($lp_type) {
3456
                case 1:
3457
                    $file = self::rl_get_resource_link_for_learnpath(
3458
                        $course_id,
3459
                        $this->get_id(),
3460
                        $item_id,
3461
                        $this->get_view_id()
3462
                    );
3463
                    switch ($lp_item_type) {
3464
                        case 'document':
3465
                            // Shows a button to download the file instead of just downloading the file directly.
3466
                            $documentPathInfo = pathinfo($file);
3467
                            if (isset($documentPathInfo['extension'])) {
3468
                                $parsed = parse_url($documentPathInfo['extension']);
3469
                                if (isset($parsed['path'])) {
3470
                                    $extension = $parsed['path'];
3471
                                    $extensionsToDownload = [
3472
                                        'zip',
3473
                                        'ppt',
3474
                                        'pptx',
3475
                                        'ods',
3476
                                        'xlsx',
3477
                                        'xls',
3478
                                        'csv',
3479
                                        'doc',
3480
                                        'docx',
3481
                                        'dot',
3482
                                    ];
3483
3484
                                    if (in_array($extension, $extensionsToDownload)) {
3485
                                        $file = api_get_path(WEB_CODE_PATH).
3486
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
3487
                                    }
3488
                                }
3489
                            }
3490
                            break;
3491
                        case 'dir':
3492
                            $file = 'lp_content.php?type=dir';
3493
                            break;
3494
                        case 'link':
3495
                            if (Link::is_youtube_link($file)) {
3496
                                $src = Link::get_youtube_video_id($file);
3497
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3498
                            } elseif (Link::isVimeoLink($file)) {
3499
                                $src = Link::getVimeoLinkId($file);
3500
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3501
                            } else {
3502
                                // If the current site is HTTPS and the link is
3503
                                // HTTP, browsers will refuse opening the link
3504
                                $urlId = api_get_current_access_url_id();
3505
                                $url = api_get_access_url($urlId, false);
3506
                                $protocol = substr($url['url'], 0, 5);
3507
                                if ('https' === $protocol) {
3508
                                    $linkProtocol = substr($file, 0, 5);
3509
                                    if ('http:' === $linkProtocol) {
3510
                                        //this is the special intervention case
3511
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3512
                                    }
3513
                                }
3514
                            }
3515
                            break;
3516
                        case 'quiz':
3517
                            // Check how much attempts of a exercise exits in lp
3518
                            $lp_item_id = $this->get_current_item_id();
3519
                            $lp_view_id = $this->get_view_id();
3520
3521
                            $prevent_reinit = null;
3522
                            if (isset($this->items[$this->current])) {
3523
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3524
                            }
3525
3526
                            if (empty($provided_toc)) {
3527
                                $list = $this->get_toc();
3528
                            } else {
3529
                                $list = $provided_toc;
3530
                            }
3531
3532
                            $type_quiz = false;
3533
                            foreach ($list as $toc) {
3534
                                if ($toc['id'] == $lp_item_id && 'quiz' == $toc['type']) {
3535
                                    $type_quiz = true;
3536
                                }
3537
                            }
3538
3539
                            if ($type_quiz) {
3540
                                $lp_item_id = (int) $lp_item_id;
3541
                                $lp_view_id = (int) $lp_view_id;
3542
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3543
                                        WHERE
3544
                                            c_id = $course_id AND
3545
                                            lp_item_id='".$lp_item_id."' AND
3546
                                            lp_view_id ='".$lp_view_id."' AND
3547
                                            status='completed'";
3548
                                $result = Database::query($sql);
3549
                                $row_count = Database:: fetch_row($result);
3550
                                $count_item_view = (int) $row_count[0];
3551
                                $not_multiple_attempt = 0;
3552
                                if (1 === $prevent_reinit && $count_item_view > 0) {
3553
                                    $not_multiple_attempt = 1;
3554
                                }
3555
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3556
                            }
3557
                            break;
3558
                    }
3559
3560
                    $tmp_array = explode('/', $file);
3561
                    $document_name = $tmp_array[count($tmp_array) - 1];
3562
                    if (strpos($document_name, '_DELETED_')) {
3563
                        $file = 'blank.php?error=document_deleted';
3564
                    }
3565
                    break;
3566
                case 2:
3567
                    if ('dir' !== $lp_item_type) {
3568
                        // Quite complex here:
3569
                        // We want to make sure 'http://' (and similar) links can
3570
                        // be loaded as is (withouth the Chamilo path in front) but
3571
                        // some contents use this form: resource.htm?resource=http://blablabla
3572
                        // which means we have to find a protocol at the path's start, otherwise
3573
                        // it should not be considered as an external URL.
3574
                        // if ($this->prerequisites_match($item_id)) {
3575
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
3576
                            if ($this->debug > 2) {
3577
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3578
                            }
3579
                            // Distant url, return as is.
3580
                            $file = $lp_item_path;
3581
                        } else {
3582
                            if ($this->debug > 2) {
3583
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3584
                            }
3585
                            // Prevent getting untranslatable urls.
3586
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3587
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3588
                            // Prepare the path.
3589
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3590
                            // TODO: Fix this for urls with protocol header.
3591
                            $file = str_replace('//', '/', $file);
3592
                            $file = str_replace(':/', '://', $file);
3593
                            if ('/' == substr($lp_path, -1)) {
3594
                                $lp_path = substr($lp_path, 0, -1);
3595
                            }
3596
3597
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3598
                                // if file not found.
3599
                                $decoded = html_entity_decode($lp_item_path);
3600
                                [$decoded] = explode('?', $decoded);
3601
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3602
                                    $file = self::rl_get_resource_link_for_learnpath(
3603
                                        $course_id,
3604
                                        $this->get_id(),
3605
                                        $item_id,
3606
                                        $this->get_view_id()
3607
                                    );
3608
                                    if (empty($file)) {
3609
                                        $file = 'blank.php?error=document_not_found';
3610
                                    } else {
3611
                                        $tmp_array = explode('/', $file);
3612
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3613
                                        if (strpos($document_name, '_DELETED_')) {
3614
                                            $file = 'blank.php?error=document_deleted';
3615
                                        } else {
3616
                                            $file = 'blank.php?error=document_not_found';
3617
                                        }
3618
                                    }
3619
                                } else {
3620
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3621
                                }
3622
                            }
3623
                        }
3624
3625
                        // We want to use parameters if they were defined in the imsmanifest
3626
                        if (false === strpos($file, 'blank.php')) {
3627
                            $lp_item_params = ltrim($lp_item_params, '?');
3628
                            $file .= (false === strstr($file, '?') ? '?' : '').$lp_item_params;
3629
                        }
3630
                    } else {
3631
                        $file = 'lp_content.php?type=dir';
3632
                    }
3633
                    break;
3634
                case 3:
3635
                    // Formatting AICC HACP append URL.
3636
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3637
                    if (!empty($lp_item_params)) {
3638
                        $aicc_append .= $lp_item_params.'&';
3639
                    }
3640
                    if ('dir' !== $lp_item_type) {
3641
                        // Quite complex here:
3642
                        // We want to make sure 'http://' (and similar) links can
3643
                        // be loaded as is (withouth the Chamilo path in front) but
3644
                        // some contents use this form: resource.htm?resource=http://blablabla
3645
                        // which means we have to find a protocol at the path's start, otherwise
3646
                        // it should not be considered as an external URL.
3647
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
3648
                            if ($this->debug > 2) {
3649
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3650
                            }
3651
                            // Distant url, return as is.
3652
                            $file = $lp_item_path;
3653
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3654
                            /*
3655
                            if (stristr($file,'<servername>') !== false) {
3656
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3657
                            }
3658
                            */
3659
                            if (false !== stripos($file, '<servername>')) {
3660
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3661
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3662
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3663
                            }
3664
3665
                            $file .= $aicc_append;
3666
                        } else {
3667
                            if ($this->debug > 2) {
3668
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3669
                            }
3670
                            // Prevent getting untranslatable urls.
3671
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3672
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3673
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3674
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3675
                            // TODO: Fix this for urls with protocol header.
3676
                            $file = str_replace('//', '/', $file);
3677
                            $file = str_replace(':/', '://', $file);
3678
                            $file .= $aicc_append;
3679
                        }
3680
                    } else {
3681
                        $file = 'lp_content.php?type=dir';
3682
                    }
3683
                    break;
3684
                case 4:
3685
                    break;
3686
                default:
3687
                    break;
3688
            }
3689
            // Replace &amp; by & because &amp; will break URL with params
3690
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3691
        }
3692
        if ($this->debug > 2) {
3693
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3694
        }
3695
3696
        return $file;
3697
    }
3698
3699
    /**
3700
     * Gets the latest usable view or generate a new one.
3701
     *
3702
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3703
     * @param int $userId      The user ID, as $this->get_user_id() is not always available
3704
     *
3705
     * @return int DB lp_view id
3706
     */
3707
    public function get_view($attempt_num = 0, $userId = null)
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
        // Check user ID.
3721
        if (empty($userId)) {
3722
            if (empty($this->get_user_id())) {
3723
                $this->error = 'User ID is empty in learnpath::get_view()';
3724
3725
                return null;
3726
            } else {
3727
                $userId = $this->get_user_id();
3728
            }
3729
        }
3730
3731
        $sql = "SELECT iid, view_count FROM $lp_view_table
3732
        		WHERE
3733
        		    c_id = $course_id AND
3734
        		    lp_id = ".$this->get_id()." AND
3735
        		    user_id = ".$userId." AND
3736
        		    session_id = $sessionId
3737
        		    $search
3738
                ORDER BY view_count DESC";
3739
        $res = Database::query($sql);
3740
        if (Database::num_rows($res) > 0) {
3741
            $row = Database::fetch_array($res);
3742
            $this->lp_view_id = $row['iid'];
3743
        } elseif (!api_is_invitee()) {
3744
            // There is no database record, create one.
3745
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3746
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3747
            Database::query($sql);
3748
            $id = Database::insert_id();
3749
            $this->lp_view_id = $id;
3750
        }
3751
3752
        return $this->lp_view_id;
3753
    }
3754
3755
    /**
3756
     * Gets the current view id.
3757
     *
3758
     * @return int View ID (from lp_view)
3759
     */
3760
    public function get_view_id()
3761
    {
3762
        if (!empty($this->lp_view_id)) {
3763
            return (int) $this->lp_view_id;
3764
        }
3765
3766
        return 0;
3767
    }
3768
3769
    /**
3770
     * Gets the update queue.
3771
     *
3772
     * @return array Array containing IDs of items to be updated by JavaScript
3773
     */
3774
    public function get_update_queue()
3775
    {
3776
        return $this->update_queue;
3777
    }
3778
3779
    /**
3780
     * Gets the user ID.
3781
     *
3782
     * @return int User ID
3783
     */
3784
    public function get_user_id()
3785
    {
3786
        if (!empty($this->user_id)) {
3787
            return (int) $this->user_id;
3788
        }
3789
3790
        return false;
3791
    }
3792
3793
    /**
3794
     * Checks if any of the items has an audio element attached.
3795
     *
3796
     * @return bool True or false
3797
     */
3798
    public function has_audio()
3799
    {
3800
        $has = false;
3801
        foreach ($this->items as $i => $item) {
3802
            if (!empty($this->items[$i]->audio)) {
3803
                $has = true;
3804
                break;
3805
            }
3806
        }
3807
3808
        return $has;
3809
    }
3810
3811
    /**
3812
     * Moves an item up and down at its level.
3813
     *
3814
     * @param int    $id        Item to move up and down
3815
     * @param string $direction Direction 'up' or 'down'
3816
     *
3817
     * @return bool|int
3818
     */
3819
    public function move_item($id, $direction)
3820
    {
3821
        $course_id = api_get_course_int_id();
3822
        if (empty($id) || empty($direction)) {
3823
            return false;
3824
        }
3825
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3826
        $sql_sel = "SELECT *
3827
                    FROM $tbl_lp_item
3828
                    WHERE
3829
                        iid = $id
3830
                    ";
3831
        $res_sel = Database::query($sql_sel);
3832
        // Check if elem exists.
3833
        if (Database::num_rows($res_sel) < 1) {
3834
            return false;
3835
        }
3836
        // Gather data.
3837
        $row = Database::fetch_array($res_sel);
3838
        $previous = $row['previous_item_id'];
3839
        $next = $row['next_item_id'];
3840
        $display = $row['display_order'];
3841
        $parent = $row['parent_item_id'];
3842
        $lp = $row['lp_id'];
3843
        // Update the item (switch with previous/next one).
3844
        switch ($direction) {
3845
            case 'up':
3846
                if ($display > 1) {
3847
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3848
                                 WHERE iid = $previous";
3849
                    $res_sel2 = Database::query($sql_sel2);
3850
                    if (Database::num_rows($res_sel2) < 1) {
3851
                        $previous_previous = 0;
3852
                    }
3853
                    // Gather data.
3854
                    $row2 = Database::fetch_array($res_sel2);
3855
                    $previous_previous = $row2['previous_item_id'];
3856
                    // Update previous_previous item (switch "next" with current).
3857
                    if (0 != $previous_previous) {
3858
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3859
                                        next_item_id = $id
3860
                                    WHERE iid = $previous_previous";
3861
                        Database::query($sql_upd2);
3862
                    }
3863
                    // Update previous item (switch with current).
3864
                    if (0 != $previous) {
3865
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3866
                                    next_item_id = $next,
3867
                                    previous_item_id = $id,
3868
                                    display_order = display_order +1
3869
                                    WHERE iid = $previous";
3870
                        Database::query($sql_upd2);
3871
                    }
3872
3873
                    // Update current item (switch with previous).
3874
                    if (0 != $id) {
3875
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3876
                                        next_item_id = $previous,
3877
                                        previous_item_id = $previous_previous,
3878
                                        display_order = display_order-1
3879
                                    WHERE c_id = ".$course_id." AND id = $id";
3880
                        Database::query($sql_upd2);
3881
                    }
3882
                    // Update next item (new previous item).
3883
                    if (!empty($next)) {
3884
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
3885
                                     WHERE iid = $next";
3886
                        Database::query($sql_upd2);
3887
                    }
3888
                    $display = $display - 1;
3889
                }
3890
                break;
3891
            case 'down':
3892
                if (0 != $next) {
3893
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3894
                                 WHERE iid = $next";
3895
                    $res_sel2 = Database::query($sql_sel2);
3896
                    if (Database::num_rows($res_sel2) < 1) {
3897
                        $next_next = 0;
3898
                    }
3899
                    // Gather data.
3900
                    $row2 = Database::fetch_array($res_sel2);
3901
                    $next_next = $row2['next_item_id'];
3902
                    // Update previous item (switch with current).
3903
                    if (0 != $previous) {
3904
                        $sql_upd2 = "UPDATE $tbl_lp_item
3905
                                     SET next_item_id = $next
3906
                                     WHERE iid = $previous";
3907
                        Database::query($sql_upd2);
3908
                    }
3909
                    // Update current item (switch with previous).
3910
                    if (0 != $id) {
3911
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3912
                                     previous_item_id = $next,
3913
                                     next_item_id = $next_next,
3914
                                     display_order = display_order + 1
3915
                                     WHERE iid = $id";
3916
                        Database::query($sql_upd2);
3917
                    }
3918
3919
                    // Update next item (new previous item).
3920
                    if (0 != $next) {
3921
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3922
                                     previous_item_id = $previous,
3923
                                     next_item_id = $id,
3924
                                     display_order = display_order-1
3925
                                     WHERE iid = $next";
3926
                        Database::query($sql_upd2);
3927
                    }
3928
3929
                    // Update next_next item (switch "previous" with current).
3930
                    if (0 != $next_next) {
3931
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3932
                                     previous_item_id = $id
3933
                                     WHERE iid = $next_next";
3934
                        Database::query($sql_upd2);
3935
                    }
3936
                    $display = $display + 1;
3937
                }
3938
                break;
3939
            default:
3940
                return false;
3941
        }
3942
3943
        return $display;
3944
    }
3945
3946
    /**
3947
     * Move a LP up (display_order).
3948
     *
3949
     * @param int $lp_id      Learnpath ID
3950
     * @param int $categoryId Category ID
3951
     *
3952
     * @return bool
3953
     */
3954
    public static function move_up($lp_id, $categoryId = 0)
3955
    {
3956
        $courseId = api_get_course_int_id();
3957
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3958
3959
        $categoryCondition = '';
3960
        if (!empty($categoryId)) {
3961
            $categoryId = (int) $categoryId;
3962
            $categoryCondition = " AND category_id = $categoryId";
3963
        }
3964
        $sql = "SELECT * FROM $lp_table
3965
                WHERE c_id = $courseId
3966
                $categoryCondition
3967
                ORDER BY display_order";
3968
        $res = Database::query($sql);
3969
        if (false === $res) {
3970
            return false;
3971
        }
3972
3973
        $lps = [];
3974
        $lp_order = [];
3975
        $num = Database::num_rows($res);
3976
        // First check the order is correct, globally (might be wrong because
3977
        // of versions < 1.8.4)
3978
        if ($num > 0) {
3979
            $i = 1;
3980
            while ($row = Database::fetch_array($res)) {
3981
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
3982
                    $sql = "UPDATE $lp_table SET display_order = $i
3983
                            WHERE iid = ".$row['iid'];
3984
                    Database::query($sql);
3985
                }
3986
                $row['display_order'] = $i;
3987
                $lps[$row['iid']] = $row;
3988
                $lp_order[$i] = $row['iid'];
3989
                $i++;
3990
            }
3991
        }
3992
        if ($num > 1) { // If there's only one element, no need to sort.
3993
            $order = $lps[$lp_id]['display_order'];
3994
            if ($order > 1) { // If it's the first element, no need to move up.
3995
                $sql = "UPDATE $lp_table SET display_order = $order
3996
                        WHERE iid = ".$lp_order[$order - 1];
3997
                Database::query($sql);
3998
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
3999
                        WHERE iid = $lp_id";
4000
                Database::query($sql);
4001
            }
4002
        }
4003
4004
        return true;
4005
    }
4006
4007
    /**
4008
     * Move a learnpath down (display_order).
4009
     *
4010
     * @param int $lp_id      Learnpath ID
4011
     * @param int $categoryId Category ID
4012
     *
4013
     * @return bool
4014
     */
4015
    public static function move_down($lp_id, $categoryId = 0)
4016
    {
4017
        $courseId = api_get_course_int_id();
4018
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4019
4020
        $categoryCondition = '';
4021
        if (!empty($categoryId)) {
4022
            $categoryId = (int) $categoryId;
4023
            $categoryCondition = " AND category_id = $categoryId";
4024
        }
4025
4026
        $sql = "SELECT * FROM $lp_table
4027
                WHERE c_id = $courseId
4028
                $categoryCondition
4029
                ORDER BY display_order";
4030
        $res = Database::query($sql);
4031
        if (false === $res) {
4032
            return false;
4033
        }
4034
        $lps = [];
4035
        $lp_order = [];
4036
        $num = Database::num_rows($res);
4037
        $max = 0;
4038
        // First check the order is correct, globally (might be wrong because
4039
        // of versions < 1.8.4).
4040
        if ($num > 0) {
4041
            $i = 1;
4042
            while ($row = Database::fetch_array($res)) {
4043
                $max = $i;
4044
                if ($row['display_order'] != $i) {
4045
                    // If we find a gap in the order, we need to fix it.
4046
                    $sql = "UPDATE $lp_table SET display_order = $i
4047
                              WHERE iid = ".$row['iid'];
4048
                    Database::query($sql);
4049
                }
4050
                $row['display_order'] = $i;
4051
                $lps[$row['iid']] = $row;
4052
                $lp_order[$i] = $row['iid'];
4053
                $i++;
4054
            }
4055
        }
4056
        if ($num > 1) { // If there's only one element, no need to sort.
4057
            $order = $lps[$lp_id]['display_order'];
4058
            if ($order < $max) { // If it's the first element, no need to move up.
4059
                $sql = "UPDATE $lp_table SET display_order = $order
4060
                        WHERE iid = ".$lp_order[$order + 1];
4061
                Database::query($sql);
4062
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4063
                        WHERE iid = $lp_id";
4064
                Database::query($sql);
4065
            }
4066
        }
4067
4068
        return true;
4069
    }
4070
4071
    /**
4072
     * Updates learnpath attributes to point to the next element
4073
     * The last part is similar to set_current_item but processing the other way around.
4074
     */
4075
    public function next()
4076
    {
4077
        if ($this->debug > 0) {
4078
            error_log('In learnpath::next()', 0);
4079
        }
4080
        $this->last = $this->get_current_item_id();
4081
        $this->items[$this->last]->save(
4082
            false,
4083
            $this->prerequisites_match($this->last)
4084
        );
4085
        $this->autocomplete_parents($this->last);
4086
        $new_index = $this->get_next_index();
4087
        if ($this->debug > 2) {
4088
            error_log('New index: '.$new_index, 0);
4089
        }
4090
        $this->index = $new_index;
4091
        if ($this->debug > 2) {
4092
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4093
        }
4094
        $this->current = $this->ordered_items[$new_index];
4095
        if ($this->debug > 2) {
4096
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4097
        }
4098
    }
4099
4100
    /**
4101
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4102
     * class, this might be redefined to allow several behaviours depending on the document type.
4103
     *
4104
     * @param int $id Resource ID
4105
     */
4106
    public function open($id)
4107
    {
4108
        // TODO:
4109
        // set the current resource attribute to this resource
4110
        // switch on element type (redefine in child class?)
4111
        // set status for this item to "opened"
4112
        // start timer
4113
        // initialise score
4114
        $this->index = 0; //or = the last item seen (see $this->last)
4115
    }
4116
4117
    /**
4118
     * Check that all prerequisites are fulfilled. Returns true and an
4119
     * empty string on success, returns false
4120
     * and the prerequisite string on error.
4121
     * This function is based on the rules for aicc_script language as
4122
     * described in the SCORM 1.2 CAM documentation page 108.
4123
     *
4124
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4125
     *
4126
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4127
     *              string otherwise
4128
     */
4129
    public function prerequisites_match($itemId = null)
4130
    {
4131
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4132
        if ($allow) {
4133
            if (api_is_allowed_to_edit() ||
4134
                api_is_platform_admin(true) ||
4135
                api_is_drh() ||
4136
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4137
            ) {
4138
                return true;
4139
            }
4140
        }
4141
4142
        $debug = $this->debug;
4143
        if ($debug > 0) {
4144
            error_log('In learnpath::prerequisites_match()');
4145
        }
4146
4147
        if (empty($itemId)) {
4148
            $itemId = $this->current;
4149
        }
4150
4151
        $currentItem = $this->getItem($itemId);
4152
4153
        if ($currentItem) {
4154
            if (2 == $this->type) {
4155
                // Getting prereq from scorm
4156
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4157
            } else {
4158
                $prereq_string = $currentItem->get_prereq_string();
4159
            }
4160
4161
            if (empty($prereq_string)) {
4162
                if ($debug > 0) {
4163
                    error_log('Found prereq_string is empty return true');
4164
                }
4165
4166
                return true;
4167
            }
4168
4169
            // Clean spaces.
4170
            $prereq_string = str_replace(' ', '', $prereq_string);
4171
            if ($debug > 0) {
4172
                error_log('Found prereq_string: '.$prereq_string, 0);
4173
            }
4174
4175
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4176
            $result = $currentItem->parse_prereq(
4177
                $prereq_string,
4178
                $this->items,
4179
                $this->refs_list,
4180
                $this->get_user_id()
4181
            );
4182
4183
            if (false === $result) {
4184
                $this->set_error_msg($currentItem->prereq_alert);
4185
            }
4186
        } else {
4187
            $result = true;
4188
            if ($debug > 1) {
4189
                error_log('$this->items['.$itemId.'] was not an object', 0);
4190
            }
4191
        }
4192
4193
        if ($debug > 1) {
4194
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4195
        }
4196
4197
        return $result;
4198
    }
4199
4200
    /**
4201
     * Updates learnpath attributes to point to the previous element
4202
     * The last part is similar to set_current_item but processing the other way around.
4203
     */
4204
    public function previous()
4205
    {
4206
        $this->last = $this->get_current_item_id();
4207
        $this->items[$this->last]->save(
4208
            false,
4209
            $this->prerequisites_match($this->last)
4210
        );
4211
        $this->autocomplete_parents($this->last);
4212
        $new_index = $this->get_previous_index();
4213
        $this->index = $new_index;
4214
        $this->current = $this->ordered_items[$new_index];
4215
    }
4216
4217
    /**
4218
     * Publishes a learnpath. This basically means show or hide the learnpath
4219
     * to normal users.
4220
     * Can be used as abstract.
4221
     *
4222
     * @param int $id         Learnpath ID
4223
     * @param int $visibility New visibility (1 = visible/published, 0= invisible/draft)
4224
     *
4225
     * @return bool
4226
     */
4227
    public static function toggleVisibility($id, $visibility = 1)
4228
    {
4229
        $repo = Container::getLpRepository();
4230
        $lp = $repo->find($id);
4231
4232
        if (!$lp) {
4233
            return false;
4234
        }
4235
4236
        $visibility = (int) $visibility;
4237
4238
        if (1 === $visibility) {
4239
            $repo->setVisibilityPublished($lp);
4240
        } else {
4241
            $repo->setVisibilityDraft($lp);
4242
        }
4243
4244
        return true;
4245
4246
        /*$action = 'visible';
4247
        if (1 != $set_visibility) {
4248
            $action = 'invisible';
4249
            self::toggle_publish($lp_id, 'i');
4250
        }
4251
4252
        return api_item_property_update(
4253
            api_get_course_info(),
4254
            TOOL_LEARNPATH,
4255
            $lp_id,
4256
            $action,
4257
            api_get_user_id()
4258
        );*/
4259
    }
4260
4261
    /**
4262
     * Publishes a learnpath category.
4263
     * This basically means show or hide the learnpath category to normal users.
4264
     *
4265
     * @param int $id
4266
     * @param int $visibility
4267
     *
4268
     * @return bool
4269
     */
4270
    public static function toggleCategoryVisibility($id, $visibility = 1)
4271
    {
4272
        $repo = Container::getLpCategoryRepository();
4273
        $resource = $repo->find($id);
4274
4275
        if (!$resource) {
4276
            return false;
4277
        }
4278
4279
        $visibility = (int) $visibility;
4280
4281
        if (1 === $visibility) {
4282
            $repo->setVisibilityPublished($resource);
4283
        } else {
4284
            $repo->setVisibilityDraft($resource);
4285
            self::toggleCategoryPublish($id, 0);
4286
        }
4287
4288
        return false;
4289
        /*
4290
        $action = 'visible';
4291
        if (1 != $visibility) {
4292
            self::toggleCategoryPublish($id, 0);
4293
            $action = 'invisible';
4294
        }
4295
4296
        return api_item_property_update(
4297
            api_get_course_info(),
4298
            TOOL_LEARNPATH_CATEGORY,
4299
            $id,
4300
            $action,
4301
            api_get_user_id()
4302
        );*/
4303
    }
4304
4305
    /**
4306
     * Publishes a learnpath. This basically means show or hide the learnpath
4307
     * on the course homepage.
4308
     *
4309
     * @param int    $id            Learnpath id
4310
     * @param string $setVisibility New visibility (v/i - visible/invisible)
4311
     *
4312
     * @return bool
4313
     */
4314
    public static function togglePublish($id, $setVisibility = 'v')
4315
    {
4316
        $addShortcut = false;
4317
        if ('v' === $setVisibility) {
4318
            $addShortcut = true;
4319
        }
4320
        $repo = Container::getLpRepository();
4321
        /** @var CLp $lp */
4322
        $lp = $repo->find($id);
4323
        if (null === $lp) {
4324
            return false;
4325
        }
4326
        $repoShortcut = Container::getShortcutRepository();
4327
        $courseEntity = api_get_course_entity();
4328
4329
        if ($addShortcut) {
4330
            $repoShortcut->addShortCut($lp, $courseEntity, $courseEntity, api_get_session_entity());
4331
        } else {
4332
            $repoShortcut->removeShortCut($lp);
4333
        }
4334
4335
        return true;
4336
4337
        /*
4338
        $course_id = api_get_course_int_id();
4339
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4340
        $lp_id = (int) $lp_id;
4341
        $sql = "SELECT * FROM $tbl_lp
4342
                WHERE iid = $lp_id";
4343
        $result = Database::query($sql);
4344
4345
        if (Database::num_rows($result)) {
4346
            $row = Database::fetch_array($result);
4347
            $name = Database::escape_string($row['name']);
4348
            if ($set_visibility == 'i') {
4349
                $v = 0;
4350
            }
4351
            if ($set_visibility == 'v') {
4352
                $v = 1;
4353
            }
4354
4355
            $session_id = api_get_session_id();
4356
            $session_condition = api_get_session_condition($session_id);
4357
4358
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4359
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4360
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4361
4362
            $sql = "SELECT * FROM $tbl_tool
4363
                    WHERE
4364
                        c_id = $course_id AND
4365
                        (link = '$link' OR link = '$oldLink') AND
4366
                        image = 'scormbuilder.gif' AND
4367
                        (
4368
                            link LIKE '$link%' OR
4369
                            link LIKE '$oldLink%'
4370
                        )
4371
                        $session_condition
4372
                    ";
4373
4374
            $result = Database::query($sql);
4375
            $num = Database::num_rows($result);
4376
            if ($set_visibility == 'i' && $num > 0) {
4377
                $sql = "DELETE FROM $tbl_tool
4378
                        WHERE
4379
                            c_id = $course_id AND
4380
                            (link = '$link' OR link = '$oldLink') AND
4381
                            image='scormbuilder.gif'
4382
                            $session_condition";
4383
                Database::query($sql);
4384
            } elseif ($set_visibility == 'v' && $num == 0) {
4385
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4386
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4387
                Database::query($sql);
4388
                $insertId = Database::insert_id();
4389
                if ($insertId) {
4390
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4391
                    Database::query($sql);
4392
                }
4393
            } elseif ($set_visibility == 'v' && $num > 0) {
4394
                $sql = "UPDATE $tbl_tool SET
4395
                            c_id = $course_id,
4396
                            name = '$name',
4397
                            link = '$link',
4398
                            image = 'scormbuilder.gif',
4399
                            visibility = '$v',
4400
                            admin = '0',
4401
                            address = 'pastillegris.gif',
4402
                            added_tool = 0,
4403
                            session_id = $session_id
4404
                        WHERE
4405
                            c_id = ".$course_id." AND
4406
                            (link = '$link' OR link = '$oldLink') AND
4407
                            image='scormbuilder.gif'
4408
                            $session_condition
4409
                        ";
4410
                Database::query($sql);
4411
            } else {
4412
                // Parameter and database incompatible, do nothing, exit.
4413
                return false;
4414
            }
4415
        } else {
4416
            return false;
4417
        }*/
4418
    }
4419
4420
    /**
4421
     * Show or hide the learnpath category on the course homepage.
4422
     *
4423
     * @param int $id
4424
     * @param int $setVisibility
4425
     *
4426
     * @return bool
4427
     */
4428
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4429
    {
4430
        $setVisibility = (int) $setVisibility;
4431
        $addShortcut = false;
4432
        if (1 === $setVisibility) {
4433
            $addShortcut = true;
4434
        }
4435
4436
        $repo = Container::getLpCategoryRepository();
4437
        /** @var CLpCategory $lp */
4438
        $category = $repo->find($id);
4439
4440
        if (null === $category) {
4441
            return false;
4442
        }
4443
4444
        $repoShortcut = Container::getShortcutRepository();
4445
        if ($addShortcut) {
4446
            $courseEntity = api_get_course_entity(api_get_course_int_id());
4447
            $repoShortcut->addShortCut($category, $courseEntity, $courseEntity, api_get_session_entity());
4448
        } else {
4449
            $repoShortcut->removeShortCut($category);
4450
        }
4451
4452
        return true;
4453
4454
        $em = Database::getManager();
0 ignored issues
show
Unused Code introduced by
$em = Database::getManager() 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...
4455
4456
        /** @var CLpCategory $category */
4457
        $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
4458
4459
        if (!$category) {
4460
            return false;
4461
        }
4462
4463
        if (empty($courseId)) {
4464
            return false;
4465
        }
4466
4467
        $link = self::getCategoryLinkForTool($id);
4468
4469
        /** @var CTool $tool */
4470
        $tool = $em->createQuery("
4471
                SELECT t FROM ChamiloCourseBundle:CTool t
4472
                WHERE
4473
                    t.course = :course AND
4474
                    t.link = :link1 AND
4475
                    t.image LIKE 'lp_category.%' AND
4476
                    t.link LIKE :link2
4477
                    $sessionCondition
4478
            ")
4479
            ->setParameters([
4480
                'course' => $courseId,
4481
                'link1' => $link,
4482
                'link2' => "$link%",
4483
            ])
4484
            ->getOneOrNullResult();
4485
4486
        if (0 == $setVisibility && $tool) {
4487
            $em->remove($tool);
4488
            $em->flush();
4489
4490
            return true;
4491
        }
4492
4493
        if (1 == $setVisibility && !$tool) {
4494
            $tool = new CTool();
4495
            $tool
4496
                ->setCategory('authoring')
4497
                ->setCourse(api_get_course_entity($courseId))
4498
                ->setName(strip_tags($category->getName()))
4499
                ->setLink($link)
4500
                ->setImage('lp_category.png')
4501
                ->setVisibility(1)
4502
                ->setAdmin(0)
4503
                ->setAddress('pastillegris.gif')
4504
                ->setAddedTool(0)
4505
                ->setSessionId($sessionId)
4506
                ->setTarget('_self');
4507
4508
            $em->persist($tool);
4509
            $em->flush();
4510
4511
            $tool->setId($tool->getIid());
4512
4513
            $em->persist($tool);
4514
            $em->flush();
4515
4516
            return true;
4517
        }
4518
4519
        if (1 == $setVisibility && $tool) {
4520
            $tool
4521
                ->setName(strip_tags($category->getName()))
4522
                ->setVisibility(1);
4523
4524
            $em->persist($tool);
4525
            $em->flush();
4526
4527
            return true;
4528
        }
4529
4530
        return false;
4531
    }
4532
4533
    /**
4534
     * Check if the learnpath category is visible for a user.
4535
     *
4536
     * @param int
4537
     * @param int
4538
     *
4539
     * @return bool
4540
     */
4541
    public static function categoryIsVisibleForStudent(
4542
        CLpCategory $category,
4543
        User $user,
4544
        $courseId = 0,
4545
        $sessionId = 0
4546
    ) {
4547
        if (empty($category)) {
4548
            return false;
4549
        }
4550
4551
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4552
4553
        if ($isAllowedToEdit) {
4554
            return true;
4555
        }
4556
4557
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4558
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4559
4560
        $courseInfo = api_get_course_info_by_id($courseId);
4561
4562
        $categoryVisibility = api_get_item_visibility(
4563
            $courseInfo,
4564
            TOOL_LEARNPATH_CATEGORY,
4565
            $category->getId(),
4566
            $sessionId
4567
        );
4568
4569
        if (1 !== $categoryVisibility && -1 != $categoryVisibility) {
4570
            return false;
4571
        }
4572
4573
        $subscriptionSettings = self::getSubscriptionSettings();
4574
4575
        if (false == $subscriptionSettings['allow_add_users_to_lp_category']) {
4576
            return true;
4577
        }
4578
4579
        $noUserSubscribed = false;
4580
        $noGroupSubscribed = true;
4581
        $users = $category->getUsers();
4582
        if (empty($users) || !$users->count()) {
4583
            $noUserSubscribed = true;
4584
        } elseif ($category->hasUserAdded($user)) {
4585
            return true;
4586
        }
4587
4588
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4589
        $em = Database::getManager();
4590
4591
        /** @var ItemPropertyRepository $itemRepo */
4592
        $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4593
4594
        /** @var CourseRepository $courseRepo */
4595
        $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4596
        $session = null;
4597
        if (!empty($sessionId)) {
4598
            $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4599
        }
4600
4601
        $course = $courseRepo->find($courseId);
4602
4603
        if (0 != $courseId) {
4604
            // Subscribed groups to a LP
4605
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4606
                    TOOL_LEARNPATH_CATEGORY,
4607
                    $category->getId(),
4608
                    $course,
4609
                    $session
4610
                );
4611
        }
4612
4613
        if (!empty($subscribedGroupsInLp)) {
4614
            $noGroupSubscribed = false;
4615
            if (!empty($groups)) {
4616
                $groups = array_column($groups, 'iid');
4617
                /** @var CItemProperty $item */
4618
                foreach ($subscribedGroupsInLp as $item) {
4619
                    if ($item->getGroup() &&
4620
                        in_array($item->getGroup()->getId(), $groups)
4621
                    ) {
4622
                        return true;
4623
                    }
4624
                }
4625
            }
4626
        }
4627
        $response = $noGroupSubscribed && $noUserSubscribed;
4628
4629
        return $response;
4630
    }
4631
4632
    /**
4633
     * Check if a learnpath category is published as course tool.
4634
     *
4635
     * @param int $courseId
4636
     *
4637
     * @return bool
4638
     */
4639
    public static function categoryIsPublished(CLpCategory $category, $courseId)
4640
    {
4641
        return false;
4642
        $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...
4643
        $em = Database::getManager();
4644
4645
        $tools = $em
4646
            ->createQuery("
4647
                SELECT t FROM ChamiloCourseBundle:CTool t
4648
                WHERE t.course = :course AND
4649
                    t.name = :name AND
4650
                    t.image LIKE 'lp_category.%' AND
4651
                    t.link LIKE :link
4652
            ")
4653
            ->setParameters([
4654
                'course' => $courseId,
4655
                'name' => strip_tags($category->getName()),
4656
                'link' => "$link%",
4657
            ])
4658
            ->getResult();
4659
4660
        /** @var CTool $tool */
4661
        $tool = current($tools);
4662
4663
        return $tool ? $tool->getVisibility() : false;
4664
    }
4665
4666
    /**
4667
     * Restart the whole learnpath. Return the URL of the first element.
4668
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4669
     * To use a similar method  statically, use the create_new_attempt() method.
4670
     *
4671
     * @return bool
4672
     */
4673
    public function restart()
4674
    {
4675
        if ($this->debug > 0) {
4676
            error_log('In learnpath::restart()', 0);
4677
        }
4678
        // TODO
4679
        // Call autosave method to save the current progress.
4680
        //$this->index = 0;
4681
        if (api_is_invitee()) {
4682
            return false;
4683
        }
4684
        $session_id = api_get_session_id();
4685
        $course_id = api_get_course_int_id();
4686
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4687
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4688
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4689
        if ($this->debug > 2) {
4690
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4691
        }
4692
        Database::query($sql);
4693
        $view_id = Database::insert_id();
4694
4695
        if ($view_id) {
4696
            $this->lp_view_id = $view_id;
4697
            $this->attempt = $this->attempt + 1;
4698
        } else {
4699
            $this->error = 'Could not insert into item_view table...';
4700
4701
            return false;
4702
        }
4703
        $this->autocomplete_parents($this->current);
4704
        foreach ($this->items as $index => $dummy) {
4705
            $this->items[$index]->restart();
4706
            $this->items[$index]->set_lp_view($this->lp_view_id);
4707
        }
4708
        $this->first();
4709
4710
        return true;
4711
    }
4712
4713
    /**
4714
     * Saves the current item.
4715
     *
4716
     * @return bool
4717
     */
4718
    public function save_current()
4719
    {
4720
        $debug = $this->debug;
4721
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4722
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4723
        if ($debug) {
4724
            error_log('save_current() saving item '.$this->current, 0);
4725
            error_log(''.print_r($this->items, true), 0);
4726
        }
4727
        if (isset($this->items[$this->current]) &&
4728
            is_object($this->items[$this->current])
4729
        ) {
4730
            if ($debug) {
4731
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4732
            }
4733
4734
            $res = $this->items[$this->current]->save(
4735
                false,
4736
                $this->prerequisites_match($this->current)
4737
            );
4738
            $this->autocomplete_parents($this->current);
4739
            $status = $this->items[$this->current]->get_status();
4740
            $this->update_queue[$this->current] = $status;
4741
4742
            if ($debug) {
4743
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4744
            }
4745
4746
            return $res;
4747
        }
4748
4749
        return false;
4750
    }
4751
4752
    /**
4753
     * Saves the given item.
4754
     *
4755
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4756
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4757
     *
4758
     * @return bool
4759
     */
4760
    public function save_item($item_id = null, $from_outside = true)
4761
    {
4762
        $debug = $this->debug;
4763
        if ($debug) {
4764
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4765
        }
4766
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4767
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4768
        if (empty($item_id)) {
4769
            $item_id = (int) $_REQUEST['id'];
4770
        }
4771
4772
        if (empty($item_id)) {
4773
            $item_id = $this->get_current_item_id();
4774
        }
4775
        if (isset($this->items[$item_id]) &&
4776
            is_object($this->items[$item_id])
4777
        ) {
4778
            if ($debug) {
4779
                error_log('Object exists');
4780
            }
4781
4782
            // Saving the item.
4783
            $res = $this->items[$item_id]->save(
4784
                $from_outside,
4785
                $this->prerequisites_match($item_id)
4786
            );
4787
4788
            if ($debug) {
4789
                error_log('update_queue before:');
4790
                error_log(print_r($this->update_queue, 1));
4791
            }
4792
            $this->autocomplete_parents($item_id);
4793
4794
            $status = $this->items[$item_id]->get_status();
4795
            $this->update_queue[$item_id] = $status;
4796
4797
            if ($debug) {
4798
                error_log('get_status(): '.$status);
4799
                error_log('update_queue after:');
4800
                error_log(print_r($this->update_queue, 1));
4801
            }
4802
4803
            return $res;
4804
        }
4805
4806
        return false;
4807
    }
4808
4809
    /**
4810
     * Saves the last item seen's ID only in case.
4811
     */
4812
    public function save_last()
4813
    {
4814
        $course_id = api_get_course_int_id();
4815
        $debug = $this->debug;
4816
        if ($debug) {
4817
            error_log('In learnpath::save_last()', 0);
4818
        }
4819
        $session_condition = api_get_session_condition(
4820
            api_get_session_id(),
4821
            true,
4822
            false
4823
        );
4824
        $table = Database::get_course_table(TABLE_LP_VIEW);
4825
4826
        $userId = $this->get_user_id();
4827
        if (empty($userId)) {
4828
            $userId = api_get_user_id();
4829
            if ($debug) {
4830
                error_log('$this->get_user_id() was empty, used api_get_user_id() instead in '.__FILE__.' line '.__LINE__);
4831
            }
4832
        }
4833
        if (isset($this->current) && !api_is_invitee()) {
4834
            if ($debug) {
4835
                error_log('Saving current item ('.$this->current.') for later review', 0);
4836
            }
4837
            $sql = "UPDATE $table SET
4838
                        last_item = ".$this->get_current_item_id()."
4839
                    WHERE
4840
                        c_id = $course_id AND
4841
                        lp_id = ".$this->get_id()." AND
4842
                        user_id = ".$userId." ".$session_condition;
4843
4844
            if ($debug) {
4845
                error_log('Saving last item seen : '.$sql, 0);
4846
            }
4847
            Database::query($sql);
4848
        }
4849
4850
        if (!api_is_invitee()) {
4851
            // Save progress.
4852
            [$progress] = $this->get_progress_bar_text('%');
4853
            $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
4854
            $scoreAsProgress = $this->getUseScoreAsProgress();
4855
            if ($scoreAsProgress && $scoreAsProgressSetting && (null === $score || empty($score) || -1 == $score)) {
4856
                if ($debug) {
4857
                    error_log("Return false: Dont save score: $score");
4858
                    error_log("progress: $progress");
4859
                }
4860
4861
                return false;
4862
            }
4863
4864
            if ($scoreAsProgress && $scoreAsProgressSetting) {
4865
                $storedProgress = self::getProgress(
4866
                    $this->get_id(),
4867
                    $userId,
4868
                    $course_id,
4869
                    $this->get_lp_session_id()
4870
                );
4871
4872
                // Check if the stored progress is higher than the new value
4873
                if ($storedProgress >= $progress) {
4874
                    if ($debug) {
4875
                        error_log("Return false: New progress value is lower than stored value - Current value: $storedProgress - New value: $progress [lp ".$this->get_id()." - user ".$userId."]");
4876
                    }
4877
4878
                    return false;
4879
                }
4880
            }
4881
            if ($progress >= 0 && $progress <= 100) {
4882
                $progress = (int) $progress;
4883
                $sql = "UPDATE $table SET
4884
                            progress = $progress
4885
                        WHERE
4886
                            c_id = $course_id AND
4887
                            lp_id = ".$this->get_id()." AND
4888
                            user_id = ".$userId." ".$session_condition;
4889
                // Ignore errors as some tables might not have the progress field just yet.
4890
                Database::query($sql);
4891
                $this->progress_db = $progress;
4892
            }
4893
        }
4894
    }
4895
4896
    /**
4897
     * Sets the current item ID (checks if valid and authorized first).
4898
     *
4899
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
4900
     */
4901
    public function set_current_item($item_id = null)
4902
    {
4903
        $debug = $this->debug;
4904
        if ($debug) {
4905
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
4906
        }
4907
        if (empty($item_id)) {
4908
            if ($debug) {
4909
                error_log('No new current item given, ignore...', 0);
4910
            }
4911
            // Do nothing.
4912
        } else {
4913
            if ($debug) {
4914
                error_log('New current item given is '.$item_id.'...', 0);
4915
            }
4916
            if (is_numeric($item_id)) {
4917
                $item_id = (int) $item_id;
4918
                // TODO: Check in database here.
4919
                $this->last = $this->current;
4920
                $this->current = $item_id;
4921
                // TODO: Update $this->index as well.
4922
                foreach ($this->ordered_items as $index => $item) {
4923
                    if ($item == $this->current) {
4924
                        $this->index = $index;
4925
                        break;
4926
                    }
4927
                }
4928
                if ($debug) {
4929
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
4930
                }
4931
            } else {
4932
                if ($debug) {
4933
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
4934
                }
4935
            }
4936
        }
4937
    }
4938
4939
    /**
4940
     * Sets the encoding.
4941
     *
4942
     * @param string $enc New encoding
4943
     *
4944
     * @return bool
4945
     *
4946
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
4947
     */
4948
    public function set_encoding($enc = 'UTF-8')
4949
    {
4950
        $enc = api_refine_encoding_id($enc);
4951
        if (empty($enc)) {
4952
            $enc = api_get_system_encoding();
4953
        }
4954
        if (api_is_encoding_supported($enc)) {
4955
            $lp = $this->get_id();
4956
            if (0 != $lp) {
4957
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4958
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
4959
                        WHERE iid = ".$lp;
4960
                $res = Database::query($sql);
4961
4962
                return $res;
4963
            }
4964
        }
4965
4966
        return false;
4967
    }
4968
4969
    /**
4970
     * Sets the JS lib setting in the database directly.
4971
     * This is the JavaScript library file this lp needs to load on startup.
4972
     *
4973
     * @param string $lib Proximity setting
4974
     *
4975
     * @return bool True on update success. False otherwise.
4976
     */
4977
    public function set_jslib($lib = '')
4978
    {
4979
        $lp = $this->get_id();
4980
4981
        if (0 != $lp) {
4982
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4983
            $lib = Database::escape_string($lib);
4984
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
4985
                    WHERE iid = $lp";
4986
            $res = Database::query($sql);
4987
4988
            return $res;
4989
        }
4990
4991
        return false;
4992
    }
4993
4994
    /**
4995
     * Set index specified prefix terms for all items in this path.
4996
     *
4997
     * @param string $terms_string Comma-separated list of terms
4998
     * @param string $prefix       Xapian term prefix
4999
     *
5000
     * @return bool False on error, true otherwise
5001
     */
5002
    public function set_terms_by_prefix($terms_string, $prefix)
5003
    {
5004
        $course_id = api_get_course_int_id();
5005
        if ('true' !== api_get_setting('search_enabled')) {
5006
            return false;
5007
        }
5008
5009
        if (!extension_loaded('xapian')) {
5010
            return false;
5011
        }
5012
5013
        $terms_string = trim($terms_string);
5014
        $terms = explode(',', $terms_string);
5015
        array_walk($terms, 'trim_value');
5016
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5017
5018
        // Don't do anything if no change, verify only at DB, not the search engine.
5019
        if ((0 == count(array_diff($terms, $stored_terms))) && (0 == count(array_diff($stored_terms, $terms)))) {
5020
            return false;
5021
        }
5022
5023
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5024
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5025
5026
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5027
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5028
        $lp_id = (int) $_POST['lp_id'];
5029
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5030
        $result = Database::query($sql);
5031
        $di = new ChamiloIndexer();
5032
5033
        while ($lp_item = Database::fetch_array($result)) {
5034
            // Get search_did.
5035
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5036
            $sql = 'SELECT * FROM %s
5037
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5038
                    LIMIT 1';
5039
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5040
5041
            //echo $sql; echo '<br>';
5042
            $res = Database::query($sql);
5043
            if (Database::num_rows($res) > 0) {
5044
                $se_ref = Database::fetch_array($res);
5045
                // Compare terms.
5046
                $doc = $di->get_document($se_ref['search_did']);
5047
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5048
                $xterms = [];
5049
                foreach ($xapian_terms as $xapian_term) {
5050
                    $xterms[] = substr($xapian_term['name'], 1);
5051
                }
5052
5053
                $dterms = $terms;
5054
                $missing_terms = array_diff($dterms, $xterms);
5055
                $deprecated_terms = array_diff($xterms, $dterms);
5056
5057
                // Save it to search engine.
5058
                foreach ($missing_terms as $term) {
5059
                    $doc->add_term($prefix.$term, 1);
5060
                }
5061
                foreach ($deprecated_terms as $term) {
5062
                    $doc->remove_term($prefix.$term);
5063
                }
5064
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5065
                $di->getDb()->flush();
5066
            }
5067
        }
5068
5069
        return true;
5070
    }
5071
5072
    /**
5073
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5074
     *
5075
     * @param int $id DB ID of the item
5076
     */
5077
    public function set_previous_item($id)
5078
    {
5079
        if ($this->debug > 0) {
5080
            error_log('In learnpath::set_previous_item()', 0);
5081
        }
5082
        $this->last = $id;
5083
    }
5084
5085
    /**
5086
     * Sets use_max_score.
5087
     *
5088
     * @param int $use_max_score Optional string giving the new location of this learnpath
5089
     *
5090
     * @return bool True on success / False on error
5091
     */
5092
    public function set_use_max_score($use_max_score = 1)
5093
    {
5094
        $use_max_score = (int) $use_max_score;
5095
        $this->use_max_score = $use_max_score;
5096
        $table = Database::get_course_table(TABLE_LP_MAIN);
5097
        $lp_id = $this->get_id();
5098
        $sql = "UPDATE $table SET
5099
                    use_max_score = '".$this->use_max_score."'
5100
                WHERE iid = $lp_id";
5101
        Database::query($sql);
5102
5103
        return true;
5104
    }
5105
5106
    /**
5107
     * Sets and saves the expired_on date.
5108
     *
5109
     * @return bool Returns true if author's name is not empty
5110
     */
5111
    public function set_modified_on()
5112
    {
5113
        $this->modified_on = api_get_utc_datetime();
5114
        $table = Database::get_course_table(TABLE_LP_MAIN);
5115
        $lp_id = $this->get_id();
5116
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5117
                WHERE iid = $lp_id";
5118
        Database::query($sql);
5119
5120
        return true;
5121
    }
5122
5123
    /**
5124
     * Sets the object's error message.
5125
     *
5126
     * @param string $error Error message. If empty, reinits the error string
5127
     */
5128
    public function set_error_msg($error = '')
5129
    {
5130
        if ($this->debug > 0) {
5131
            error_log('In learnpath::set_error_msg()', 0);
5132
        }
5133
        if (empty($error)) {
5134
            $this->error = '';
5135
        } else {
5136
            $this->error .= $error;
5137
        }
5138
    }
5139
5140
    /**
5141
     * Launches the current item if not 'sco'
5142
     * (starts timer and make sure there is a record ready in the DB).
5143
     *
5144
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5145
     *
5146
     * @return bool
5147
     */
5148
    public function start_current_item($allow_new_attempt = false)
5149
    {
5150
        $debug = $this->debug;
5151
        if ($debug) {
5152
            error_log('In learnpath::start_current_item()');
5153
            error_log('current: '.$this->current);
5154
        }
5155
        if (0 != $this->current && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5156
            $type = $this->get_type();
5157
            $item_type = $this->items[$this->current]->get_type();
5158
            if ((2 == $type && 'sco' != $item_type) ||
5159
                (3 == $type && 'au' != $item_type) ||
5160
                (1 == $type && TOOL_QUIZ != $item_type && TOOL_HOTPOTATOES != $item_type)
5161
            ) {
5162
                if ($debug) {
5163
                    error_log('item type: '.$item_type);
5164
                    error_log('lp type: '.$type);
5165
                }
5166
                $this->items[$this->current]->open($allow_new_attempt);
5167
                $this->autocomplete_parents($this->current);
5168
                $prereq_check = $this->prerequisites_match($this->current);
5169
                if ($debug) {
5170
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5171
                }
5172
                $this->items[$this->current]->save(false, $prereq_check);
5173
            }
5174
            // If sco, then it is supposed to have been updated by some other call.
5175
            if ('sco' == $item_type) {
5176
                $this->items[$this->current]->restart();
5177
            }
5178
        }
5179
        if ($debug) {
5180
            error_log('lp_view_session_id');
5181
            error_log($this->lp_view_session_id);
5182
            error_log('api session id');
5183
            error_log(api_get_session_id());
5184
            error_log('End of learnpath::start_current_item()');
5185
        }
5186
5187
        return true;
5188
    }
5189
5190
    /**
5191
     * Stops the processing and counters for the old item (as held in $this->last).
5192
     *
5193
     * @return bool True/False
5194
     */
5195
    public function stop_previous_item()
5196
    {
5197
        $debug = $this->debug;
5198
        if ($debug) {
5199
            error_log('In learnpath::stop_previous_item()', 0);
5200
        }
5201
5202
        if (0 != $this->last && $this->last != $this->current &&
5203
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5204
        ) {
5205
            if ($debug) {
5206
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5207
            }
5208
            switch ($this->get_type()) {
5209
                case '3':
5210
                    if ('au' != $this->items[$this->last]->get_type()) {
5211
                        if ($debug) {
5212
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5213
                        }
5214
                        $this->items[$this->last]->close();
5215
                    } else {
5216
                        if ($debug) {
5217
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5218
                        }
5219
                    }
5220
                    break;
5221
                case '2':
5222
                    if ('sco' != $this->items[$this->last]->get_type()) {
5223
                        if ($debug) {
5224
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5225
                        }
5226
                        $this->items[$this->last]->close();
5227
                    } else {
5228
                        if ($debug) {
5229
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5230
                        }
5231
                    }
5232
                    break;
5233
                case '1':
5234
                default:
5235
                    if ($debug) {
5236
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5237
                    }
5238
                    $this->items[$this->last]->close();
5239
                    break;
5240
            }
5241
        } else {
5242
            if ($debug) {
5243
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5244
            }
5245
5246
            return false;
5247
        }
5248
5249
        return true;
5250
    }
5251
5252
    /**
5253
     * Updates the default view mode from fullscreen to embedded and inversely.
5254
     *
5255
     * @return string The current default view mode ('fullscreen' or 'embedded')
5256
     */
5257
    public function update_default_view_mode()
5258
    {
5259
        $table = Database::get_course_table(TABLE_LP_MAIN);
5260
        $sql = "SELECT * FROM $table
5261
                WHERE iid = ".$this->get_id();
5262
        $res = Database::query($sql);
5263
        if (Database::num_rows($res) > 0) {
5264
            $row = Database::fetch_array($res);
5265
            $default_view_mode = $row['default_view_mod'];
5266
            $view_mode = $default_view_mode;
5267
            switch ($default_view_mode) {
5268
                case 'fullscreen': // default with popup
5269
                    $view_mode = 'embedded';
5270
                    break;
5271
                case 'embedded': // default view with left menu
5272
                    $view_mode = 'embedframe';
5273
                    break;
5274
                case 'embedframe': //folded menu
5275
                    $view_mode = 'impress';
5276
                    break;
5277
                case 'impress':
5278
                    $view_mode = 'fullscreen';
5279
                    break;
5280
            }
5281
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5282
                    WHERE iid = ".$this->get_id();
5283
            Database::query($sql);
5284
            $this->mode = $view_mode;
5285
5286
            return $view_mode;
5287
        }
5288
5289
        return -1;
5290
    }
5291
5292
    /**
5293
     * Updates the default behaviour about auto-commiting SCORM updates.
5294
     *
5295
     * @return bool True if auto-commit has been set to 'on', false otherwise
5296
     */
5297
    public function update_default_scorm_commit()
5298
    {
5299
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5300
        $sql = "SELECT * FROM $lp_table
5301
                WHERE iid = ".$this->get_id();
5302
        $res = Database::query($sql);
5303
        if (Database::num_rows($res) > 0) {
5304
            $row = Database::fetch_array($res);
5305
            $force = $row['force_commit'];
5306
            if (1 == $force) {
5307
                $force = 0;
5308
                $force_return = false;
5309
            } elseif (0 == $force) {
5310
                $force = 1;
5311
                $force_return = true;
5312
            }
5313
            $sql = "UPDATE $lp_table SET force_commit = $force
5314
                    WHERE iid = ".$this->get_id();
5315
            Database::query($sql);
5316
            $this->force_commit = $force_return;
5317
5318
            return $force_return;
5319
        }
5320
5321
        return -1;
5322
    }
5323
5324
    /**
5325
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5326
     *
5327
     * @return bool True on success, false on failure
5328
     */
5329
    public function update_display_order()
5330
    {
5331
        $course_id = api_get_course_int_id();
5332
        $table = Database::get_course_table(TABLE_LP_MAIN);
5333
        $sql = "SELECT * FROM $table
5334
                WHERE c_id = $course_id
5335
                ORDER BY display_order";
5336
        $res = Database::query($sql);
5337
        if (false === $res) {
5338
            return false;
5339
        }
5340
5341
        $num = Database::num_rows($res);
5342
        // First check the order is correct, globally (might be wrong because
5343
        // of versions < 1.8.4).
5344
        if ($num > 0) {
5345
            $i = 1;
5346
            while ($row = Database::fetch_array($res)) {
5347
                if ($row['display_order'] != $i) {
5348
                    // If we find a gap in the order, we need to fix it.
5349
                    $sql = "UPDATE $table SET display_order = $i
5350
                            WHERE iid = ".$row['iid'];
5351
                    Database::query($sql);
5352
                }
5353
                $i++;
5354
            }
5355
        }
5356
5357
        return true;
5358
    }
5359
5360
    /**
5361
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5362
     *
5363
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5364
     */
5365
    public function update_reinit()
5366
    {
5367
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5368
        $sql = "SELECT * FROM $lp_table
5369
                WHERE iid = ".$this->get_id();
5370
        $res = Database::query($sql);
5371
        if (Database::num_rows($res) > 0) {
5372
            $row = Database::fetch_array($res);
5373
            $force = $row['prevent_reinit'];
5374
            if (1 == $force) {
5375
                $force = 0;
5376
            } elseif (0 == $force) {
5377
                $force = 1;
5378
            }
5379
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5380
                    WHERE iid = ".$this->get_id();
5381
            Database::query($sql);
5382
            $this->prevent_reinit = $force;
5383
5384
            return $force;
5385
        }
5386
5387
        return -1;
5388
    }
5389
5390
    /**
5391
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5392
     *
5393
     * @return string 'single', 'multi' or 'seriousgame'
5394
     *
5395
     * @author ndiechburg <[email protected]>
5396
     */
5397
    public function get_attempt_mode()
5398
    {
5399
        //Set default value for seriousgame_mode
5400
        if (!isset($this->seriousgame_mode)) {
5401
            $this->seriousgame_mode = 0;
5402
        }
5403
        // Set default value for prevent_reinit
5404
        if (!isset($this->prevent_reinit)) {
5405
            $this->prevent_reinit = 1;
5406
        }
5407
        if (1 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
5408
            return 'seriousgame';
5409
        }
5410
        if (0 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
5411
            return 'single';
5412
        }
5413
        if (0 == $this->seriousgame_mode && 0 == $this->prevent_reinit) {
5414
            return 'multiple';
5415
        }
5416
5417
        return 'single';
5418
    }
5419
5420
    /**
5421
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5422
     *
5423
     * @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...
5424
     *
5425
     * @return bool
5426
     *
5427
     * @author ndiechburg <[email protected]>
5428
     */
5429
    public function set_attempt_mode($mode)
5430
    {
5431
        switch ($mode) {
5432
            case 'seriousgame':
5433
                $sg_mode = 1;
5434
                $prevent_reinit = 1;
5435
                break;
5436
            case 'single':
5437
                $sg_mode = 0;
5438
                $prevent_reinit = 1;
5439
                break;
5440
            case 'multiple':
5441
                $sg_mode = 0;
5442
                $prevent_reinit = 0;
5443
                break;
5444
            default:
5445
                $sg_mode = 0;
5446
                $prevent_reinit = 0;
5447
                break;
5448
        }
5449
        $this->prevent_reinit = $prevent_reinit;
5450
        $this->seriousgame_mode = $sg_mode;
5451
        $table = Database::get_course_table(TABLE_LP_MAIN);
5452
        $sql = "UPDATE $table SET
5453
                prevent_reinit = $prevent_reinit ,
5454
                seriousgame_mode = $sg_mode
5455
                WHERE iid = ".$this->get_id();
5456
        $res = Database::query($sql);
5457
        if ($res) {
5458
            return true;
5459
        } else {
5460
            return false;
5461
        }
5462
    }
5463
5464
    /**
5465
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5466
     *
5467
     * @author ndiechburg <[email protected]>
5468
     */
5469
    public function switch_attempt_mode()
5470
    {
5471
        $mode = $this->get_attempt_mode();
5472
        switch ($mode) {
5473
            case 'single':
5474
                $next_mode = 'multiple';
5475
                break;
5476
            case 'multiple':
5477
                $next_mode = 'seriousgame';
5478
                break;
5479
            case 'seriousgame':
5480
            default:
5481
                $next_mode = 'single';
5482
                break;
5483
        }
5484
        $this->set_attempt_mode($next_mode);
5485
    }
5486
5487
    /**
5488
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5489
     * but possibility to do again a completed item.
5490
     *
5491
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5492
     *
5493
     * @author ndiechburg <[email protected]>
5494
     */
5495
    public function set_seriousgame_mode()
5496
    {
5497
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5498
        $sql = "SELECT * FROM $lp_table
5499
                WHERE iid = ".$this->get_id();
5500
        $res = Database::query($sql);
5501
        if (Database::num_rows($res) > 0) {
5502
            $row = Database::fetch_array($res);
5503
            $force = $row['seriousgame_mode'];
5504
            if (1 == $force) {
5505
                $force = 0;
5506
            } elseif (0 == $force) {
5507
                $force = 1;
5508
            }
5509
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5510
			        WHERE iid = ".$this->get_id();
5511
            Database::query($sql);
5512
            $this->seriousgame_mode = $force;
5513
5514
            return $force;
5515
        }
5516
5517
        return -1;
5518
    }
5519
5520
    /**
5521
     * Updates the "scorm_debug" value that shows or hide the debug window.
5522
     *
5523
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5524
     */
5525
    public function update_scorm_debug()
5526
    {
5527
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5528
        $sql = "SELECT * FROM $lp_table
5529
                WHERE iid = ".$this->get_id();
5530
        $res = Database::query($sql);
5531
        if (Database::num_rows($res) > 0) {
5532
            $row = Database::fetch_array($res);
5533
            $force = $row['debug'];
5534
            if (1 == $force) {
5535
                $force = 0;
5536
            } elseif (0 == $force) {
5537
                $force = 1;
5538
            }
5539
            $sql = "UPDATE $lp_table SET debug = $force
5540
                    WHERE iid = ".$this->get_id();
5541
            Database::query($sql);
5542
            $this->scorm_debug = $force;
5543
5544
            return $force;
5545
        }
5546
5547
        return -1;
5548
    }
5549
5550
    /**
5551
     * Function that makes a call to the function sort_tree_array and create_tree_array.
5552
     *
5553
     * @author Kevin Van Den Haute
5554
     *
5555
     * @param  array
5556
     */
5557
    public function tree_array($array)
5558
    {
5559
        $array = $this->sort_tree_array($array);
5560
        $this->create_tree_array($array);
5561
    }
5562
5563
    /**
5564
     * Creates an array with the elements of the learning path tree in it.
5565
     *
5566
     * @author Kevin Van Den Haute
5567
     *
5568
     * @param array $array
5569
     * @param int   $parent
5570
     * @param int   $depth
5571
     * @param array $tmp
5572
     */
5573
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
5574
    {
5575
        if (is_array($array)) {
5576
            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...
5577
                if ($array[$i]['parent_item_id'] == $parent) {
5578
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
5579
                        $tmp[] = $array[$i]['parent_item_id'];
5580
                        $depth++;
5581
                    }
5582
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
5583
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
5584
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
5585
5586
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
5587
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
5588
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
5589
                    $this->arrMenu[] = [
5590
                        'id' => $array[$i]['id'],
5591
                        'ref' => $ref,
5592
                        'item_type' => $array[$i]['item_type'],
5593
                        'title' => $array[$i]['title'],
5594
                        'title_raw' => $array[$i]['title_raw'],
5595
                        'path' => $path,
5596
                        'description' => $array[$i]['description'],
5597
                        'parent_item_id' => $array[$i]['parent_item_id'],
5598
                        'previous_item_id' => $array[$i]['previous_item_id'],
5599
                        'next_item_id' => $array[$i]['next_item_id'],
5600
                        'min_score' => $array[$i]['min_score'],
5601
                        'max_score' => $array[$i]['max_score'],
5602
                        'mastery_score' => $array[$i]['mastery_score'],
5603
                        'display_order' => $array[$i]['display_order'],
5604
                        'prerequisite' => $preq,
5605
                        'depth' => $depth,
5606
                        'audio' => $audio,
5607
                        'prerequisite_min_score' => $prerequisiteMinScore,
5608
                        'prerequisite_max_score' => $prerequisiteMaxScore,
5609
                    ];
5610
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
5611
                }
5612
            }
5613
        }
5614
    }
5615
5616
    /**
5617
     * Sorts a multi dimensional array by parent id and display order.
5618
     *
5619
     * @author Kevin Van Den Haute
5620
     *
5621
     * @param array $array (array with al the learning path items in it)
5622
     *
5623
     * @return array
5624
     */
5625
    public function sort_tree_array($array)
5626
    {
5627
        foreach ($array as $key => $row) {
5628
            $parent[$key] = $row['parent_item_id'];
5629
            $position[$key] = $row['display_order'];
5630
        }
5631
5632
        if (count($array) > 0) {
5633
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
5634
        }
5635
5636
        return $array;
5637
    }
5638
5639
    /**
5640
     * Function that creates a html list of learning path items so that we can add audio files to them.
5641
     *
5642
     * @author Kevin Van Den Haute
5643
     *
5644
     * @return string
5645
     */
5646
    public function overview()
5647
    {
5648
        $return = '';
5649
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
5650
5651
        // we need to start a form when we want to update all the mp3 files
5652
        if ('true' == $update_audio) {
5653
            $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">';
5654
        }
5655
        $return .= '<div id="message"></div>';
5656
        if (0 == count($this->items)) {
5657
            $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');
5658
        } else {
5659
            $return_audio = '<table class="table table-hover table-striped data_table">';
5660
            $return_audio .= '<tr>';
5661
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
5662
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
5663
            $return_audio .= '</tr>';
5664
5665
            if ('true' != $update_audio) {
5666
                $return .= '<div class="col-md-12">';
5667
                $return .= self::return_new_tree($update_audio);
5668
                $return .= '</div>';
5669
                $return .= Display::div(
5670
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
5671
                    ['style' => 'float:left; margin-top:15px;width:100%']
5672
                );
5673
            } else {
5674
                $return_audio .= self::return_new_tree($update_audio);
5675
                $return .= $return_audio.'</table>';
5676
            }
5677
5678
            // We need to close the form when we are updating the mp3 files.
5679
            if ('true' == $update_audio) {
5680
                $return .= '<div class="footer-audio">';
5681
                $return .= Display::button(
5682
                    'save_audio',
5683
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('Save audio and organization'),
5684
                    ['class' => 'btn btn-primary', 'type' => 'submit']
5685
                );
5686
                $return .= '</div>';
5687
            }
5688
        }
5689
5690
        // We need to close the form when we are updating the mp3 files.
5691
        if ('true' == $update_audio && isset($this->arrMenu) && 0 != count($this->arrMenu)) {
5692
            $return .= '</form>';
5693
        }
5694
5695
        return $return;
5696
    }
5697
5698
    /**
5699
     * @param string $update_audio
5700
     *
5701
     * @return array
5702
     */
5703
    public function processBuildMenuElements($update_audio = 'false')
5704
    {
5705
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
5706
        $arrLP = $this->getItemsForForm();
5707
5708
        $this->tree_array($arrLP);
5709
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
5710
        unset($this->arrMenu);
5711
        $default_data = null;
5712
        $default_content = null;
5713
        $elements = [];
5714
        $return_audio = null;
5715
        $iconPath = api_get_path(SYS_PUBLIC_PATH).'img/';
5716
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
5717
        $countItems = count($arrLP);
5718
5719
        $upIcon = Display::return_icon(
5720
            'up.png',
5721
            get_lang('Up'),
5722
            [],
5723
            ICON_SIZE_TINY
5724
        );
5725
5726
        $disableUpIcon = Display::return_icon(
5727
            'up_na.png',
5728
            get_lang('Up'),
5729
            [],
5730
            ICON_SIZE_TINY
5731
        );
5732
5733
        $downIcon = Display::return_icon(
5734
            'down.png',
5735
            get_lang('Down'),
5736
            [],
5737
            ICON_SIZE_TINY
5738
        );
5739
5740
        $disableDownIcon = Display::return_icon(
5741
            'down_na.png',
5742
            get_lang('Down'),
5743
            [],
5744
            ICON_SIZE_TINY
5745
        );
5746
5747
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
5748
5749
        $pluginCalendar = 'true' === api_get_plugin_setting('learning_calendar', 'enabled');
5750
        $plugin = null;
5751
        if ($pluginCalendar) {
5752
            $plugin = LearningCalendarPlugin::create();
5753
        }
5754
5755
        for ($i = 0; $i < $countItems; $i++) {
5756
            $parent_id = $arrLP[$i]['parent_item_id'];
5757
            $title = $arrLP[$i]['title'];
5758
            $title_cut = $arrLP[$i]['title_raw'];
5759
            if (false === $show) {
5760
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
5761
            }
5762
            // Link for the documents
5763
            if ('document' === $arrLP[$i]['item_type'] || TOOL_READOUT_TEXT === $arrLP[$i]['item_type']) {
5764
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5765
                $title_cut = Display::url(
5766
                    $title_cut,
5767
                    $url,
5768
                    [
5769
                        'class' => 'ajax moved',
5770
                        'data-title' => $title,
5771
                        'title' => $title,
5772
                    ]
5773
                );
5774
            }
5775
5776
            // Detect if type is FINAL_ITEM to set path_id to SESSION
5777
            if (TOOL_LP_FINAL_ITEM === $arrLP[$i]['item_type']) {
5778
                Session::write('pathItem', $arrLP[$i]['path']);
5779
            }
5780
5781
            $oddClass = 'row_even';
5782
            if (0 == ($i % 2)) {
5783
                $oddClass = 'row_odd';
5784
            }
5785
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
5786
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
5787
5788
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
5789
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
5790
            } else {
5791
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
5792
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
5793
                } else {
5794
                    if (TOOL_LP_FINAL_ITEM === $arrLP[$i]['item_type']) {
5795
                        $icon = Display::return_icon('certificate.png');
5796
                    } else {
5797
                        $icon = Display::return_icon('folder_document.png');
5798
                    }
5799
                }
5800
            }
5801
5802
            // The audio column.
5803
            $return_audio .= '<td align="left" style="padding-left:10px;">';
5804
            $audio = '';
5805
            if (!$update_audio || 'true' != $update_audio) {
5806
                if (empty($arrLP[$i]['audio'])) {
5807
                    $audio .= '';
5808
                }
5809
            } else {
5810
                $types = self::getChapterTypes();
5811
                if (!in_array($arrLP[$i]['item_type'], $types)) {
5812
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
5813
                    if (!empty($arrLP[$i]['audio'])) {
5814
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
5815
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('Remove audio');
5816
                    }
5817
                }
5818
            }
5819
5820
            $return_audio .= Display::span($icon.' '.$title).
5821
                Display::tag(
5822
                    'td',
5823
                    $audio,
5824
                    ['style' => '']
5825
                );
5826
            $return_audio .= '</td>';
5827
            $move_icon = '';
5828
            $move_item_icon = '';
5829
            $edit_icon = '';
5830
            $delete_icon = '';
5831
            $audio_icon = '';
5832
            $prerequisities_icon = '';
5833
            $forumIcon = '';
5834
            $previewIcon = '';
5835
            $pluginCalendarIcon = '';
5836
            $orderIcons = '';
5837
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
5838
5839
            if ($is_allowed_to_edit) {
5840
                if (!$update_audio || 'true' != $update_audio) {
5841
                    if (TOOL_LP_FINAL_ITEM !== $arrLP[$i]['item_type']) {
5842
                        $move_icon .= '<a class="moved" href="#">';
5843
                        $move_icon .= Display::return_icon(
5844
                            'move_everywhere.png',
5845
                            get_lang('Move'),
5846
                            [],
5847
                            ICON_SIZE_TINY
5848
                        );
5849
                        $move_icon .= '</a>';
5850
                    }
5851
                }
5852
5853
                // No edit for this item types
5854
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
5855
                    if ('dir' != $arrLP[$i]['item_type']) {
5856
                        $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">';
5857
                        $edit_icon .= Display::return_icon(
5858
                            'edit.png',
5859
                            get_lang('Edit section description/name'),
5860
                            [],
5861
                            ICON_SIZE_TINY
5862
                        );
5863
                        $edit_icon .= '</a>';
5864
5865
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
5866
                            $forumThread = null;
5867
                            if (isset($this->items[$arrLP[$i]['id']])) {
5868
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
5869
                                    $this->course_int_id,
5870
                                    $this->lp_session_id
5871
                                );
5872
                            }
5873
                            if ($forumThread) {
5874
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
5875
                                        'action' => 'dissociate_forum',
5876
                                        'id' => $arrLP[$i]['id'],
5877
                                        'lp_id' => $this->lp_id,
5878
                                    ]);
5879
                                $forumIcon = Display::url(
5880
                                    Display::return_icon(
5881
                                        'forum.png',
5882
                                        get_lang('Dissociate the forum of this learning path item'),
5883
                                        [],
5884
                                        ICON_SIZE_TINY
5885
                                    ),
5886
                                    $forumIconUrl,
5887
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
5888
                                );
5889
                            } else {
5890
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
5891
                                        'action' => 'create_forum',
5892
                                        'id' => $arrLP[$i]['id'],
5893
                                        'lp_id' => $this->lp_id,
5894
                                    ]);
5895
                                $forumIcon = Display::url(
5896
                                    Display::return_icon(
5897
                                        'forum.png',
5898
                                        get_lang('Associate a forum to this learning path item'),
5899
                                        [],
5900
                                        ICON_SIZE_TINY
5901
                                    ),
5902
                                    $forumIconUrl,
5903
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
5904
                                );
5905
                            }
5906
                        }
5907
                    } else {
5908
                        $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">';
5909
                        $edit_icon .= Display::return_icon(
5910
                            'edit.png',
5911
                            get_lang('Edit section description/name'),
5912
                            [],
5913
                            ICON_SIZE_TINY
5914
                        );
5915
                        $edit_icon .= '</a>';
5916
                    }
5917
                } else {
5918
                    if (TOOL_LP_FINAL_ITEM == $arrLP[$i]['item_type']) {
5919
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
5920
                        $edit_icon .= Display::return_icon(
5921
                            'edit.png',
5922
                            get_lang('Edit'),
5923
                            [],
5924
                            ICON_SIZE_TINY
5925
                        );
5926
                        $edit_icon .= '</a>';
5927
                    }
5928
                }
5929
5930
                if ($pluginCalendar) {
5931
                    $pluginLink = $pluginUrl.
5932
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5933
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('1 day'), [], ICON_SIZE_TINY);
5934
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
5935
                    if ($itemInfo && 1 == $itemInfo['value']) {
5936
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('1 day'), [], ICON_SIZE_TINY);
5937
                    }
5938
                    $pluginCalendarIcon = Display::url(
5939
                        $iconCalendar,
5940
                        $pluginLink,
5941
                        ['class' => 'btn btn-default']
5942
                    );
5943
                }
5944
5945
                if ('final_item' != $arrLP[$i]['item_type']) {
5946
                    $orderIcons = Display::url(
5947
                        $upIcon,
5948
                        'javascript:void(0)',
5949
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
5950
                    );
5951
                    $orderIcons .= Display::url(
5952
                        $downIcon,
5953
                        'javascript:void(0)',
5954
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
5955
                    );
5956
                }
5957
5958
                $delete_icon .= ' <a
5959
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
5960
                    onclick="return confirmation(\''.addslashes($title).'\');"
5961
                    class="btn btn-default">';
5962
                $delete_icon .= Display::return_icon(
5963
                    'delete.png',
5964
                    get_lang('Delete section'),
5965
                    [],
5966
                    ICON_SIZE_TINY
5967
                );
5968
                $delete_icon .= '</a>';
5969
5970
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5971
                $previewImage = Display::return_icon(
5972
                    'preview_view.png',
5973
                    get_lang('Preview'),
5974
                    [],
5975
                    ICON_SIZE_TINY
5976
                );
5977
5978
                switch ($arrLP[$i]['item_type']) {
5979
                    case TOOL_DOCUMENT:
5980
                    case TOOL_LP_FINAL_ITEM:
5981
                    case TOOL_READOUT_TEXT:
5982
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5983
                        $previewIcon = Display::url(
5984
                            $previewImage,
5985
                            $urlPreviewLink,
5986
                            [
5987
                                'target' => '_blank',
5988
                                'class' => 'btn btn-default',
5989
                                'data-title' => $arrLP[$i]['title'],
5990
                                'title' => $arrLP[$i]['title'],
5991
                            ]
5992
                        );
5993
                        break;
5994
                    case TOOL_THREAD:
5995
                    case TOOL_FORUM:
5996
                    case TOOL_QUIZ:
5997
                    case TOOL_STUDENTPUBLICATION:
5998
                    case TOOL_LINK:
5999
                        $class = 'btn btn-default';
6000
                        $target = '_blank';
6001
                        $link = self::rl_get_resource_link_for_learnpath(
6002
                            $this->course_int_id,
6003
                            $this->lp_id,
6004
                            $arrLP[$i]['id'],
6005
                            0
6006
                        );
6007
                        $previewIcon = Display::url(
6008
                            $previewImage,
6009
                            $link,
6010
                            [
6011
                                'class' => $class,
6012
                                'data-title' => $arrLP[$i]['title'],
6013
                                'title' => $arrLP[$i]['title'],
6014
                                'target' => $target,
6015
                            ]
6016
                        );
6017
                        break;
6018
                    default:
6019
                        $previewIcon = Display::url(
6020
                            $previewImage,
6021
                            $url.'&action=view_item',
6022
                            ['class' => 'btn btn-default', 'target' => '_blank']
6023
                        );
6024
                        break;
6025
                }
6026
6027
                if ('dir' != $arrLP[$i]['item_type']) {
6028
                    $prerequisities_icon = Display::url(
6029
                        Display::return_icon(
6030
                            'accept.png',
6031
                            get_lang('Prerequisites'),
6032
                            [],
6033
                            ICON_SIZE_TINY
6034
                        ),
6035
                        $url.'&action=edit_item_prereq',
6036
                        ['class' => 'btn btn-default']
6037
                    );
6038
                    if ('final_item' != $arrLP[$i]['item_type']) {
6039
                        /*$move_item_icon = Display::url(
6040
                            Display::return_icon(
6041
                                'move.png',
6042
                                get_lang('Move'),
6043
                                [],
6044
                                ICON_SIZE_TINY
6045
                            ),
6046
                            $url.'&action=move_item',
6047
                            ['class' => 'btn btn-default']
6048
                        );*/
6049
                    }
6050
                    $audio_icon = Display::url(
6051
                        Display::return_icon(
6052
                            'audio.png',
6053
                            get_lang('Upload'),
6054
                            [],
6055
                            ICON_SIZE_TINY
6056
                        ),
6057
                        $url.'&action=add_audio',
6058
                        ['class' => 'btn btn-default']
6059
                    );
6060
                }
6061
            }
6062
            if ('true' != $update_audio) {
6063
                $row = $move_icon.' '.$icon.
6064
                    Display::span($title_cut).
6065
                    Display::tag(
6066
                        'div',
6067
                        "<div class=\"btn-group btn-group-xs\">
6068
                                    $previewIcon
6069
                                    $audio
6070
                                    $edit_icon
6071
                                    $pluginCalendarIcon
6072
                                    $forumIcon
6073
                                    $prerequisities_icon
6074
                                    $move_item_icon
6075
                                    $audio_icon
6076
                                    $orderIcons
6077
                                    $delete_icon
6078
                                </div>",
6079
                        ['class' => 'btn-toolbar button_actions']
6080
                    );
6081
            } else {
6082
                $row =
6083
                    Display::span($title.$icon).
6084
                    Display::span($audio, ['class' => 'button_actions']);
6085
            }
6086
6087
            $default_data[$arrLP[$i]['id']] = $row;
6088
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6089
6090
            if (empty($parent_id)) {
6091
                $elements[$arrLP[$i]['id']]['data'] = $row;
6092
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6093
            } else {
6094
                $parent_arrays = [];
6095
                if ($arrLP[$i]['depth'] > 1) {
6096
                    // Getting list of parents
6097
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6098
                        foreach ($arrLP as $item) {
6099
                            if ($item['id'] == $parent_id) {
6100
                                if (0 == $item['parent_item_id']) {
6101
                                    $parent_id = $item['id'];
6102
                                    break;
6103
                                } else {
6104
                                    $parent_id = $item['parent_item_id'];
6105
                                    if (empty($parent_arrays)) {
6106
                                        $parent_arrays[] = intval($item['id']);
6107
                                    }
6108
                                    $parent_arrays[] = $parent_id;
6109
                                    break;
6110
                                }
6111
                            }
6112
                        }
6113
                    }
6114
                }
6115
6116
                if (!empty($parent_arrays)) {
6117
                    $parent_arrays = array_reverse($parent_arrays);
6118
                    $val = '$elements';
6119
                    $x = 0;
6120
                    foreach ($parent_arrays as $item) {
6121
                        if ($x != count($parent_arrays) - 1) {
6122
                            $val .= '["'.$item.'"]["children"]';
6123
                        } else {
6124
                            $val .= '["'.$item.'"]["children"]';
6125
                        }
6126
                        $x++;
6127
                    }
6128
                    $val .= "";
6129
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6130
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6131
                } else {
6132
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6133
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6134
                }
6135
            }
6136
        }
6137
6138
        return [
6139
            'elements' => $elements,
6140
            'default_data' => $default_data,
6141
            'default_content' => $default_content,
6142
            'return_audio' => $return_audio,
6143
        ];
6144
    }
6145
6146
    /**
6147
     * @param string $updateAudio true/false strings
6148
     *
6149
     * @return string
6150
     */
6151
    public function returnLpItemList($updateAudio)
6152
    {
6153
        $result = $this->processBuildMenuElements($updateAudio);
6154
6155
        $html = self::print_recursive(
6156
            $result['elements'],
6157
            $result['default_data'],
6158
            $result['default_content']
6159
        );
6160
6161
        if (!empty($html)) {
6162
            $html .= Display::return_message(get_lang('Drag and drop an element here'));
6163
        }
6164
6165
        return $html;
6166
    }
6167
6168
    /**
6169
     * @param string $update_audio
6170
     * @param bool   $drop_element_here
6171
     *
6172
     * @return string
6173
     */
6174
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6175
    {
6176
        $result = $this->processBuildMenuElements($update_audio);
6177
6178
        $list = '<ul id="lp_item_list">';
6179
        $tree = $this->print_recursive(
6180
            $result['elements'],
6181
            $result['default_data'],
6182
            $result['default_content']
6183
        );
6184
6185
        if (!empty($tree)) {
6186
            $list .= $tree;
6187
        } else {
6188
            if ($drop_element_here) {
6189
                $list .= Display::return_message(get_lang('Drag and drop an element here'));
6190
            }
6191
        }
6192
        $list .= '</ul>';
6193
6194
        $return = Display::panelCollapse(
6195
            $this->name,
6196
            $list,
6197
            'scorm-list',
6198
            null,
6199
            'scorm-list-accordion',
6200
            'scorm-list-collapse'
6201
        );
6202
6203
        if ('true' === $update_audio) {
6204
            $return = $result['return_audio'];
6205
        }
6206
6207
        return $return;
6208
    }
6209
6210
    /**
6211
     * @param array $elements
6212
     * @param array $default_data
6213
     * @param array $default_content
6214
     *
6215
     * @return string
6216
     */
6217
    public function print_recursive($elements, $default_data, $default_content)
6218
    {
6219
        $return = '';
6220
        foreach ($elements as $key => $item) {
6221
            if (isset($item['load_data']) || empty($item['data'])) {
6222
                $item['data'] = $default_data[$item['load_data']];
6223
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6224
            }
6225
            $sub_list = '';
6226
            if (isset($item['type']) && 'dir' === $item['type']) {
6227
                // empty value
6228
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6229
            }
6230
            if (empty($item['children'])) {
6231
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6232
                $active = null;
6233
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6234
                    $active = 'active';
6235
                }
6236
                $return .= Display::tag(
6237
                    'li',
6238
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6239
                    ['id' => $key, 'class' => 'record li_container']
6240
                );
6241
            } else {
6242
                // Sections
6243
                $data = '';
6244
                if (isset($item['children'])) {
6245
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6246
                }
6247
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6248
                $return .= Display::tag(
6249
                    'li',
6250
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6251
                    ['id' => $key, 'class' => 'record li_container']
6252
                );
6253
            }
6254
        }
6255
6256
        return $return;
6257
    }
6258
6259
    /**
6260
     * This function builds the action menu.
6261
     *
6262
     * @param bool   $returnString           Optional
6263
     * @param bool   $showRequirementButtons Optional. Allow show the requirements button
6264
     * @param bool   $isConfigPage           Optional. If is the config page, show the edit button
6265
     * @param bool   $allowExpand            Optional. Allow show the expand/contract button
6266
     * @param string $action
6267
     * @param array  $extraField
6268
     *
6269
     * @return string
6270
     */
6271
    public function build_action_menu(
6272
        $returnString = false,
6273
        $showRequirementButtons = true,
6274
        $isConfigPage = false,
6275
        $allowExpand = true,
6276
        $action = '',
6277
        $extraField = []
6278
    ) {
6279
        $actionsRight = '';
6280
        $lpId = $this->lp_id;
6281
        if (!isset($extraField['backTo']) && empty($extraField['backTo'])) {
6282
            $back = Display::url(
6283
                Display::return_icon(
6284
                    'back.png',
6285
                    get_lang('Back to learning paths'),
6286
                    '',
6287
                    ICON_SIZE_MEDIUM
6288
                ),
6289
                'lp_controller.php?'.api_get_cidreq()
6290
            );
6291
        } else {
6292
            $back = Display::url(
6293
                Display::return_icon(
6294
                    'back.png',
6295
                    get_lang('Back'),
6296
                    '',
6297
                    ICON_SIZE_MEDIUM
6298
                ),
6299
                $extraField['backTo']
6300
            );
6301
        }
6302
6303
        /*if ($backToBuild) {
6304
            $back = Display::url(
6305
                Display::return_icon(
6306
                    'back.png',
6307
                    get_lang('GoBack'),
6308
                    '',
6309
                    ICON_SIZE_MEDIUM
6310
                ),
6311
                "lp_controller.php?action=add_item&type=step&lp_id=$lpId&".api_get_cidreq()
6312
            );
6313
        }*/
6314
6315
        $actionsLeft = $back;
6316
6317
        $actionsLeft .= Display::url(
6318
            Display::return_icon(
6319
                'preview_view.png',
6320
                get_lang('Preview'),
6321
                '',
6322
                ICON_SIZE_MEDIUM
6323
            ),
6324
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6325
                'action' => 'view',
6326
                'lp_id' => $lpId,
6327
                'isStudentView' => 'true',
6328
            ])
6329
        );
6330
6331
        $actionsLeft .= Display::url(
6332
            Display::return_icon(
6333
                'upload_audio.png',
6334
                get_lang('Add audio'),
6335
                '',
6336
                ICON_SIZE_MEDIUM
6337
            ),
6338
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6339
                'action' => 'admin_view',
6340
                'lp_id' => $lpId,
6341
                'updateaudio' => 'true',
6342
            ])
6343
        );
6344
6345
        $subscriptionSettings = self::getSubscriptionSettings();
6346
6347
        $request = api_request_uri();
6348
        if (false === strpos($request, 'edit')) {
6349
            $actionsLeft .= Display::url(
6350
                Display::return_icon(
6351
                    'settings.png',
6352
                    get_lang('Course settings'),
6353
                    '',
6354
                    ICON_SIZE_MEDIUM
6355
                ),
6356
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6357
                    'action' => 'edit',
6358
                    'lp_id' => $lpId,
6359
                ])
6360
            );
6361
        }
6362
6363
        if ((false === strpos($request, 'build') &&
6364
            false === strpos($request, 'add_item')) ||
6365
            in_array($action, ['add_audio'])
6366
        ) {
6367
            $actionsLeft .= Display::url(
6368
                Display::return_icon(
6369
                    'edit.png',
6370
                    get_lang('Edit'),
6371
                    '',
6372
                    ICON_SIZE_MEDIUM
6373
                ),
6374
                'lp_controller.php?'.http_build_query([
6375
                    'action' => 'build',
6376
                    'lp_id' => $lpId,
6377
                ]).'&'.api_get_cidreq()
6378
            );
6379
        }
6380
6381
        if (false === strpos(api_get_self(), 'lp_subscribe_users.php')) {
6382
            if (1 == $this->subscribeUsers &&
6383
                $subscriptionSettings['allow_add_users_to_lp']) {
6384
                $actionsLeft .= Display::url(
6385
                    Display::return_icon(
6386
                        'user.png',
6387
                        get_lang('Subscribe users to learning path'),
6388
                        '',
6389
                        ICON_SIZE_MEDIUM
6390
                    ),
6391
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=$lpId&".api_get_cidreq()
6392
                );
6393
            }
6394
        }
6395
6396
        if ($allowExpand) {
6397
            $actionsLeft .= Display::url(
6398
                Display::return_icon(
6399
                    'expand.png',
6400
                    get_lang('Expand'),
6401
                    ['id' => 'expand'],
6402
                    ICON_SIZE_MEDIUM
6403
                ).
6404
                Display::return_icon(
6405
                    'contract.png',
6406
                    get_lang('Collapse'),
6407
                    ['id' => 'contract', 'class' => 'hide'],
6408
                    ICON_SIZE_MEDIUM
6409
                ),
6410
                '#',
6411
                ['role' => 'button', 'id' => 'hide_bar_template']
6412
            );
6413
        }
6414
6415
        if ($showRequirementButtons) {
6416
            $buttons = [
6417
                [
6418
                    'title' => get_lang('Set previous step as prerequisite for each step'),
6419
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6420
                        'action' => 'set_previous_step_as_prerequisite',
6421
                        'lp_id' => $lpId,
6422
                    ]),
6423
                ],
6424
                [
6425
                    'title' => get_lang('Clear all prerequisites'),
6426
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6427
                        'action' => 'clear_prerequisites',
6428
                        'lp_id' => $lpId,
6429
                    ]),
6430
                ],
6431
            ];
6432
            $actionsRight = Display::groupButtonWithDropDown(
6433
                get_lang('Prerequisites options'),
6434
                $buttons,
6435
                true
6436
            );
6437
        }
6438
6439
        if (api_is_platform_admin() && isset($extraField['authorlp'])) {
6440
            $actionsLeft .= Display::url(
6441
                Display::return_icon(
6442
                    'add-groups.png',
6443
                    get_lang('Author'),
6444
                    '',
6445
                    ICON_SIZE_MEDIUM
6446
                ),
6447
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6448
                    'action' => 'author_view',
6449
                    'lp_id' => $lpId,
6450
                ])
6451
            );
6452
        }
6453
6454
        $toolbar = Display::toolbarAction(
6455
            'actions-lp-controller',
6456
            [$actionsLeft, $actionsRight]
6457
        );
6458
6459
        if ($returnString) {
6460
            return $toolbar;
6461
        }
6462
6463
        echo $toolbar;
6464
    }
6465
6466
    /**
6467
     * Creates the default learning path folder.
6468
     *
6469
     * @param array $course
6470
     * @param int   $creatorId
6471
     *
6472
     * @return bool
6473
     */
6474
    public static function generate_learning_path_folder($course, $creatorId = 0)
6475
    {
6476
        // Creating learning_path folder
6477
        $dir = 'learning_path';
6478
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6479
        $folder = false;
6480
6481
        $folderData = create_unexisting_directory(
6482
            $course,
6483
            $creatorId,
6484
            0,
6485
            null,
6486
            0,
6487
            '',
6488
            $dir,
6489
            get_lang('Learning paths'),
6490
            0
6491
        );
6492
6493
        if (!empty($folderData)) {
6494
            $folder = true;
6495
        }
6496
6497
        return $folder;
6498
    }
6499
6500
    /**
6501
     * @param array  $course
6502
     * @param string $lp_name
6503
     * @param int    $creatorId
6504
     *
6505
     * @return array
6506
     */
6507
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6508
    {
6509
        $filepath = '';
6510
        $dir = '/learning_path/';
6511
6512
        if (empty($lp_name)) {
6513
            $lp_name = $this->name;
6514
        }
6515
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6516
        $folder = self::generate_learning_path_folder($course, $creatorId);
6517
6518
        // Limits title size
6519
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6520
        $dir = $dir.$title;
6521
6522
        // Creating LP folder
6523
        $documentId = null;
6524
        if ($folder) {
6525
            $folderData = create_unexisting_directory(
6526
                $course,
6527
                $creatorId,
6528
                0,
6529
                0,
6530
                0,
6531
                $filepath,
6532
                $dir,
6533
                $lp_name
6534
            );
6535
            if (!empty($folderData)) {
6536
                $folder = true;
6537
            }
6538
6539
            $documentId = $folderData->getIid();
6540
            $dir = $dir.'/';
6541
            if ($folder) {
6542
                // $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6543
            }
6544
        }
6545
6546
        if (empty($documentId)) {
6547
            $dir = api_remove_trailing_slash($dir);
6548
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6549
        }
6550
6551
        $array = [
6552
            'dir' => $dir,
6553
            'filepath' => $filepath,
6554
            'folder' => $folder,
6555
            'id' => $documentId,
6556
        ];
6557
6558
        return $array;
6559
    }
6560
6561
    /**
6562
     * Create a new document //still needs some finetuning.
6563
     *
6564
     * @param array  $courseInfo
6565
     * @param string $content
6566
     * @param string $title
6567
     * @param string $extension
6568
     * @param int    $parentId
6569
     * @param int    $creatorId  creator id
6570
     *
6571
     * @return int
6572
     */
6573
    public function create_document(
6574
        $courseInfo,
6575
        $content = '',
6576
        $title = '',
6577
        $extension = 'html',
6578
        $parentId = 0,
6579
        $creatorId = 0
6580
    ) {
6581
        if (!empty($courseInfo)) {
6582
            $course_id = $courseInfo['real_id'];
6583
        } else {
6584
            $course_id = api_get_course_int_id();
6585
        }
6586
6587
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6588
        $sessionId = api_get_session_id();
6589
6590
        // Generates folder
6591
        $result = $this->generate_lp_folder($courseInfo);
6592
        $dir = $result['dir'];
6593
6594
        if (empty($parentId) || '/' == $parentId) {
6595
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6596
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6597
6598
            if ('/' === $parentId) {
6599
                $dir = '/';
6600
            }
6601
6602
            // Please, do not modify this dirname formatting.
6603
            if (strstr($dir, '..')) {
6604
                $dir = '/';
6605
            }
6606
6607
            if (!empty($dir[0]) && '.' == $dir[0]) {
6608
                $dir = substr($dir, 1);
6609
            }
6610
            if (!empty($dir[0]) && '/' != $dir[0]) {
6611
                $dir = '/'.$dir;
6612
            }
6613
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
6614
                $dir .= '/';
6615
            }
6616
        } else {
6617
            $parentInfo = DocumentManager::get_document_data_by_id(
6618
                $parentId,
6619
                $courseInfo['code']
6620
            );
6621
            if (!empty($parentInfo)) {
6622
                $dir = $parentInfo['path'].'/';
6623
            }
6624
        }
6625
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6626
        // is already escaped twice when it gets here.
6627
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6628
        if (!empty($title)) {
6629
            $title = api_replace_dangerous_char(stripslashes($title));
6630
        } else {
6631
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6632
        }
6633
6634
        $title = disable_dangerous_file($title);
6635
        $filename = $title;
6636
        $content = !empty($content) ? $content : $_POST['content_lp'];
6637
        $tmp_filename = $filename;
6638
        /*$i = 0;
6639
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
6640
            $tmp_filename = $filename.'_'.++$i;
6641
        }*/
6642
        $filename = $tmp_filename.'.'.$extension;
6643
6644
        if ('html' === $extension) {
6645
            $content = stripslashes($content);
6646
            $content = str_replace(
6647
                api_get_path(WEB_COURSE_PATH),
6648
                api_get_path(REL_PATH).'courses/',
6649
                $content
6650
            );
6651
6652
            // Change the path of mp3 to absolute.
6653
            // The first regexp deals with :// urls.
6654
            $content = preg_replace(
6655
                "|(flashvars=\"file=)([^:/]+)/|",
6656
                "$1".api_get_path(
6657
                    REL_COURSE_PATH
6658
                ).$courseInfo['path'].'/document/',
6659
                $content
6660
            );
6661
            // The second regexp deals with audio/ urls.
6662
            $content = preg_replace(
6663
                "|(flashvars=\"file=)([^/]+)/|",
6664
                "$1".api_get_path(
6665
                    REL_COURSE_PATH
6666
                ).$courseInfo['path'].'/document/$2/',
6667
                $content
6668
            );
6669
            // For flv player: To prevent edition problem with firefox,
6670
            // we have to use a strange tip (don't blame me please).
6671
            $content = str_replace(
6672
                '</body>',
6673
                '<style type="text/css">body{}</style></body>',
6674
                $content
6675
            );
6676
        }
6677
6678
        $save_file_path = $dir.$filename;
6679
6680
        $document = DocumentManager::addDocument(
6681
            $courseInfo,
6682
            $save_file_path,
6683
            'file',
6684
            '',
6685
            $tmp_filename,
6686
            '',
6687
            0, //readonly
6688
            true,
6689
            null,
6690
            $sessionId,
6691
            $creatorId,
6692
            false,
6693
            $content,
6694
            $parentId
6695
        );
6696
6697
        $document_id = $document->getIid();
6698
        if ($document_id) {
6699
            $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
6700
            $new_title = $originalTitle;
6701
6702
            if ($new_comment || $new_title) {
6703
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
6704
                $ct = '';
6705
                if ($new_comment) {
6706
                    $ct .= ", comment='".Database::escape_string($new_comment)."'";
6707
                }
6708
                if ($new_title) {
6709
                    $ct .= ", title='".Database::escape_string($new_title)."' ";
6710
                }
6711
6712
                $sql = "UPDATE $tbl_doc SET ".substr($ct, 1)."
6713
                        WHERE iid = $document_id ";
6714
                Database::query($sql);
6715
            }
6716
        }
6717
6718
        return $document_id;
6719
    }
6720
6721
    /**
6722
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
6723
     */
6724
    public function edit_document()
6725
    {
6726
        $repo = Container::getDocumentRepository();
6727
        if (isset($_REQUEST['document_id']) && !empty($_REQUEST['document_id'])) {
6728
            $id = (int) $_REQUEST['document_id'];
6729
            /** @var CDocument $document */
6730
            $document = $repo->find($id);
6731
6732
            if ($document->getResourceNode()->hasEditableTextContent()) {
6733
                $repo->updateResourceFileContent($document, $_REQUEST['content_lp']);
6734
            }
6735
6736
            $document->setTitle($_REQUEST['title']);
6737
            $repo->update($document);
6738
        }
6739
    }
6740
6741
    /**
6742
     * Displays the selected item, with a panel for manipulating the item.
6743
     *
6744
     * @param CLpItem $lpItem
6745
     * @param string  $msg
6746
     * @param bool    $show_actions
6747
     *
6748
     * @return string
6749
     */
6750
    public function display_item($lpItem, $msg = null, $show_actions = true)
6751
    {
6752
        $course_id = api_get_course_int_id();
6753
        $return = '';
6754
6755
        if (empty($lpItem)) {
6756
            return '';
6757
        }
6758
        $item_id = $lpItem->getIid();
6759
        $itemType = $lpItem->getItemType();
6760
        $lpId = $lpItem->getLp()->getIid();
6761
        $path = $lpItem->getPath();
6762
6763
        Session::write('parent_item_id', 'dir' === $itemType ? $item_id : 0);
6764
6765
        // Prevents wrong parent selection for document, see Bug#1251.
6766
        if ('dir' !== $itemType) {
6767
            Session::write('parent_item_id', $lpItem->getParentItemId());
6768
        }
6769
6770
        if ($show_actions) {
6771
            $return .= $this->displayItemMenu($lpItem);
6772
        }
6773
        $return .= '<div style="padding:10px;">';
6774
6775
        if ('' != $msg) {
6776
            $return .= $msg;
6777
        }
6778
6779
        $return .= '<h3>'.$lpItem->getTitle().'</h3>';
6780
6781
        switch ($itemType) {
6782
            case TOOL_THREAD:
6783
                $link = $this->rl_get_resource_link_for_learnpath(
6784
                    $course_id,
6785
                    $lpId,
6786
                    $item_id,
6787
                    0
6788
                );
6789
                $return .= Display::url(
6790
                    get_lang('Go to thread'),
6791
                    $link,
6792
                    ['class' => 'btn btn-primary']
6793
                );
6794
                break;
6795
            case TOOL_FORUM:
6796
                $return .= Display::url(
6797
                    get_lang('Go to the forum'),
6798
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$path,
6799
                    ['class' => 'btn btn-primary']
6800
                );
6801
                break;
6802
            case TOOL_QUIZ:
6803
                if (!empty($path)) {
6804
                    $exercise = new Exercise();
6805
                    $exercise->read($path);
6806
                    $return .= $exercise->description.'<br />';
6807
                    $return .= Display::url(
6808
                        get_lang('Go to exercise'),
6809
                        api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
6810
                        ['class' => 'btn btn-primary']
6811
                    );
6812
                }
6813
                break;
6814
            case TOOL_LP_FINAL_ITEM:
6815
                $return .= $this->getSavedFinalItem();
6816
                break;
6817
            case TOOL_DOCUMENT:
6818
            case TOOL_READOUT_TEXT:
6819
                $repo = Container::getDocumentRepository();
6820
                /** @var CDocument $document */
6821
                $document = $repo->find($lpItem->getPath());
6822
                $return .= $this->display_document($document, true, true);
6823
                break;
6824
            case TOOL_HOTPOTATOES:
6825
                $return .= $this->display_document($document, false, true);
6826
                break;
6827
        }
6828
        $return .= '</div>';
6829
6830
        return $return;
6831
    }
6832
6833
    /**
6834
     * Shows the needed forms for editing a specific item.
6835
     *
6836
     * @param CLpItem $lpItem
6837
     *
6838
     * @throws Exception
6839
     * @throws HTML_QuickForm_Error
6840
     *
6841
     * @return string
6842
     */
6843
    public function display_edit_item($lpItem, $excludeExtraFields = [])
6844
    {
6845
        $course_id = api_get_course_int_id();
6846
        $return = '';
6847
6848
        if (empty($lpItem)) {
6849
            return '';
6850
        }
6851
        $item_id = $lpItem->getIid();
6852
        $itemType = $lpItem->getItemType();
6853
        $path = $lpItem->getPath();
6854
6855
        switch ($itemType) {
6856
            case 'dir':
6857
            case 'asset':
6858
            case 'sco':
6859
                if (isset($_GET['view']) && 'build' === $_GET['view']) {
6860
                    $return .= $this->displayItemMenu($lpItem);
6861
                    $return .= $this->display_item_form(
6862
                        $lpItem,
6863
                        'edit'
6864
                    );
6865
                } else {
6866
                    $return .= $this->display_item_form(
6867
                        $lpItem,
6868
                        'edit_item'
6869
                    );
6870
                }
6871
                break;
6872
            case TOOL_LP_FINAL_ITEM:
6873
            case TOOL_DOCUMENT:
6874
            case TOOL_READOUT_TEXT:
6875
                $return .= $this->displayItemMenu($lpItem);
6876
                $return .= $this->displayDocumentForm('edit', $lpItem);
6877
                break;
6878
            case TOOL_LINK:
6879
                $link = null;
6880
                if (!empty($path)) {
6881
                    $repo = Container::getLinkRepository();
6882
                    $link = $repo->find($path);
6883
                }
6884
                $return .= $this->displayItemMenu($lpItem);
6885
                $return .= $this->display_link_form('edit', $lpItem, $link);
6886
6887
                break;
6888
            case TOOL_QUIZ:
6889
                if (!empty($path)) {
6890
                    $repo = Container::getQuizRepository();
6891
                    $resource = $repo->find($path);
6892
                }
6893
                $return .= $this->displayItemMenu($lpItem);
6894
                $return .= $this->display_quiz_form('edit', $lpItem, $resource);
6895
                break;
6896
            /*case TOOL_HOTPOTATOES:
6897
                $return .= $this->displayItemMenu($lpItem);
6898
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
6899
                break;*/
6900
            case TOOL_STUDENTPUBLICATION:
6901
                if (!empty($path)) {
6902
                    $repo = Container::getStudentPublicationRepository();
6903
                    $resource = $repo->find($path);
6904
                }
6905
                $return .= $this->displayItemMenu($lpItem);
6906
                $return .= $this->display_student_publication_form('edit', $lpItem, $resource);
6907
                break;
6908
            case TOOL_FORUM:
6909
                if (!empty($path)) {
6910
                    $repo = Container::getForumRepository();
6911
                    $resource = $repo->find($path);
6912
                }
6913
                $return .= $this->displayItemMenu($lpItem);
6914
                $return .= $this->display_forum_form('edit', $lpItem, $resource);
6915
                break;
6916
            case TOOL_THREAD:
6917
                if (!empty($path)) {
6918
                    $repo = Container::getForumPostRepository();
6919
                    $resource = $repo->find($path);
6920
                }
6921
                $return .= $this->displayItemMenu($lpItem);
6922
                $return .= $this->display_thread_form('edit', $lpItem, $resource);
6923
                break;
6924
        }
6925
6926
        return $return;
6927
    }
6928
6929
    /**
6930
     * Function that displays a list with al the resources that
6931
     * could be added to the learning path.
6932
     *
6933
     * @throws Exception
6934
     * @throws HTML_QuickForm_Error
6935
     *
6936
     * @return bool
6937
     */
6938
    public function displayResources()
6939
    {
6940
        // Get all the docs.
6941
        $documents = $this->get_documents(true);
6942
6943
        // Get all the exercises.
6944
        $exercises = $this->get_exercises();
6945
6946
        // Get all the links.
6947
        $links = $this->get_links();
6948
6949
        // Get all the student publications.
6950
        $works = $this->get_student_publications();
6951
6952
        // Get all the forums.
6953
        $forums = $this->get_forums();
6954
6955
        // Get the final item form (see BT#11048) .
6956
        $finish = $this->getFinalItemForm();
6957
6958
        $headers = [
6959
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
6960
            Display::return_icon('quiz.png', get_lang('Tests'), [], ICON_SIZE_BIG),
6961
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
6962
            Display::return_icon('works.png', get_lang('Assignments'), [], ICON_SIZE_BIG),
6963
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
6964
            Display::return_icon('add_learnpath_section.png', get_lang('Add section'), [], ICON_SIZE_BIG),
6965
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
6966
        ];
6967
6968
        echo Display::return_message(
6969
            get_lang('Click on the [Learner view] button to see your learning path'),
6970
            'normal'
6971
        );
6972
        $section = $this->displayNewSectionForm();
6973
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
6974
6975
        echo Display::tabs(
6976
            $headers,
6977
            [
6978
                $documents,
6979
                $exercises,
6980
                $links,
6981
                $works,
6982
                $forums,
6983
                $section,
6984
                $finish,
6985
            ],
6986
            'resource_tab',
6987
            [],
6988
            [],
6989
            $selected
6990
        );
6991
6992
        return true;
6993
    }
6994
6995
    /**
6996
     * Returns the extension of a document.
6997
     *
6998
     * @param string $filename
6999
     *
7000
     * @return string Extension (part after the last dot)
7001
     */
7002
    public function get_extension($filename)
7003
    {
7004
        $explode = explode('.', $filename);
7005
7006
        return $explode[count($explode) - 1];
7007
    }
7008
7009
    /**
7010
     * @return string
7011
     */
7012
    public function getCurrentBuildingModeURL()
7013
    {
7014
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
7015
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
7016
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
7017
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
7018
7019
        $currentUrl = api_get_self().'?'.api_get_cidreq().
7020
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
7021
7022
        return $currentUrl;
7023
    }
7024
7025
    /**
7026
     * Displays a document by id.
7027
     *
7028
     * @param CDocument $document
7029
     * @param bool      $show_title
7030
     * @param bool      $iframe
7031
     * @param bool      $edit_link
7032
     *
7033
     * @return string
7034
     */
7035
    public function display_document($document, $show_title = false, $iframe = true, $edit_link = false)
7036
    {
7037
        $return = '';
7038
        if (!$document) {
7039
            return '';
7040
        }
7041
7042
        $repo = Container::getDocumentRepository();
7043
7044
        // TODO: Add a path filter.
7045
        if ($iframe) {
7046
            //$url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.str_replace('%2F', '/', urlencode($row_doc['path'])).'?'.api_get_cidreq();
7047
            $url = $repo->getResourceFileUrl($document);
7048
7049
            $return .= '<iframe
7050
                id="learnpath_preview_frame"
7051
                frameborder="0"
7052
                height="400"
7053
                width="100%"
7054
                scrolling="auto"
7055
                src="'.$url.'"></iframe>';
7056
        } else {
7057
            $return = $repo->getResourceFileContent($document);
7058
        }
7059
7060
        return $return;
7061
    }
7062
7063
    /**
7064
     * Return HTML form to add/edit a link item.
7065
     *
7066
     * @param string  $action (add/edit)
7067
     * @param CLpItem $lpItem
7068
     * @param CLink   $link
7069
     *
7070
     * @throws Exception
7071
     * @throws HTML_QuickForm_Error
7072
     *
7073
     * @return string HTML form
7074
     */
7075
    public function display_link_form($action = 'add', $lpItem, $link)
7076
    {
7077
        $item_url = '';
7078
        if ($link) {
7079
            $item_url = stripslashes($link->getUrl());
7080
        }
7081
        $form = new FormValidator(
7082
            'edit_link',
7083
            'POST',
7084
            $this->getCurrentBuildingModeURL()
7085
        );
7086
7087
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7088
7089
        $urlAttributes = ['class' => 'learnpath_item_form'];
7090
        $urlAttributes['disabled'] = 'disabled';
7091
        $form->addElement('url', 'url', get_lang('URL'), $urlAttributes);
7092
        $form->setDefault('url', $item_url);
7093
7094
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7095
7096
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7097
    }
7098
7099
    /**
7100
     * Return HTML form to add/edit a quiz.
7101
     *
7102
     * @param string  $action   Action (add/edit)
7103
     * @param CLpItem $lpItem   Item ID if already exists
7104
     * @param CQuiz   $exercise Extra information (quiz ID if integer)
7105
     *
7106
     * @throws Exception
7107
     *
7108
     * @return string HTML form
7109
     */
7110
    public function display_quiz_form($action = 'add', $lpItem, $exercise)
7111
    {
7112
        $form = new FormValidator(
7113
            'quiz_form',
7114
            'POST',
7115
            $this->getCurrentBuildingModeURL()
7116
        );
7117
7118
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7119
7120
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7121
7122
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7123
    }
7124
7125
    /**
7126
     * Return the form to display the forum edit/add option.
7127
     *
7128
     * @param CLpItem $lpItem
7129
     *
7130
     * @throws Exception
7131
     *
7132
     * @return string HTML form
7133
     */
7134
    public function display_forum_form($action = 'add', $lpItem, $resource)
7135
    {
7136
        $form = new FormValidator(
7137
            'forum_form',
7138
            'POST',
7139
            $this->getCurrentBuildingModeURL()
7140
        );
7141
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7142
7143
        if ('add' === $action) {
7144
            $form->addButtonSave(get_lang('Add forum to course'), 'submit_button');
7145
        } else {
7146
            $form->addButtonSave(get_lang('Edit the current forum'), 'submit_button');
7147
        }
7148
7149
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7150
    }
7151
7152
    /**
7153
     * Return HTML form to add/edit forum threads.
7154
     *
7155
     * @param string  $action
7156
     * @param CLpItem $lpItem
7157
     * @param string  $resource
7158
     *
7159
     * @throws Exception
7160
     *
7161
     * @return string HTML form
7162
     */
7163
    public function display_thread_form($action = 'add', $lpItem, $resource)
7164
    {
7165
        $form = new FormValidator(
7166
            'thread_form',
7167
            'POST',
7168
            $this->getCurrentBuildingModeURL()
7169
        );
7170
7171
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7172
7173
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7174
7175
        return $form->returnForm();
7176
    }
7177
7178
    /**
7179
     * Return the HTML form to display an item (generally a dir item).
7180
     *
7181
     * @param CLpItem $lpItem
7182
     * @param string  $action
7183
     *
7184
     * @throws Exception
7185
     * @throws HTML_QuickForm_Error
7186
     *
7187
     * @return string HTML form
7188
     */
7189
    public function display_item_form(
7190
        $lpItem,
7191
        $action = 'add_item'
7192
    ) {
7193
        $item_type = $lpItem->getItemType();
7194
7195
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
7196
7197
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
7198
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7199
7200
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
7201
7202
        return $form->returnForm();
7203
    }
7204
7205
    /**
7206
     * Return HTML form to add/edit a student publication (work).
7207
     *
7208
     * @param string              $action
7209
     * @param CStudentPublication $resource
7210
     *
7211
     * @throws Exception
7212
     *
7213
     * @return string HTML form
7214
     */
7215
    public function display_student_publication_form(
7216
        $action = 'add',
7217
        CLpItem $lpItem,
7218
        $resource
7219
    ) {
7220
        $form = new FormValidator('frm_student_publication', 'post', '#');
7221
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7222
7223
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7224
7225
        $return = '<div class="sectioncomment">';
7226
        $return .= $form->returnForm();
7227
        $return .= '</div>';
7228
7229
        return $return;
7230
    }
7231
7232
    public function displayNewSectionForm()
7233
    {
7234
        $action = 'add_item';
7235
        $item_type = 'dir';
7236
7237
        $lpItem = new CLpItem();
7238
        $lpItem->setItemType('dir');
7239
7240
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
7241
7242
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
7243
        LearnPathItemForm::setForm($form, 'add', $this, $lpItem);
7244
7245
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
7246
        $form->addElement('hidden', 'type', 'dir');
7247
7248
        return $form->returnForm();
7249
    }
7250
7251
    /**
7252
     * Returns the form to update or create a document.
7253
     *
7254
     * @param string  $action (add/edit)
7255
     * @param CLpItem $lpItem
7256
     *
7257
     * @throws HTML_QuickForm_Error
7258
     * @throws Exception
7259
     *
7260
     * @return string HTML form
7261
     */
7262
    public function displayDocumentForm($action = 'add', $lpItem = null)
7263
    {
7264
        if (empty($lpItem)) {
7265
            return '';
7266
        }
7267
7268
        $courseInfo = api_get_course_info();
7269
7270
        $form = new FormValidator(
7271
            'form',
7272
            'POST',
7273
            $this->getCurrentBuildingModeURL(),
7274
            '',
7275
            ['enctype' => 'multipart/form-data']
7276
        );
7277
7278
        $data = $this->generate_lp_folder($courseInfo);
7279
7280
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7281
7282
        switch ($action) {
7283
            case 'add':
7284
                $folders = DocumentManager::get_all_document_folders(
7285
                    $courseInfo,
7286
                    0,
7287
                    true
7288
                );
7289
                DocumentManager::build_directory_selector(
7290
                    $folders,
7291
                    '',
7292
                    [],
7293
                    true,
7294
                    $form,
7295
                    'directory_parent_id'
7296
                );
7297
7298
                if (isset($data['id'])) {
7299
                    $defaults['directory_parent_id'] = $data['id'];
7300
                }
7301
7302
                break;
7303
        }
7304
7305
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7306
7307
        return $form->returnForm();
7308
    }
7309
7310
    /**
7311
     * @param array  $courseInfo
7312
     * @param string $content
7313
     * @param string $title
7314
     * @param int    $parentId
7315
     *
7316
     * @throws \Doctrine\ORM\ORMException
7317
     * @throws \Doctrine\ORM\OptimisticLockException
7318
     * @throws \Doctrine\ORM\TransactionRequiredException
7319
     *
7320
     * @return int
7321
     */
7322
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
7323
    {
7324
        $creatorId = api_get_user_id();
7325
        $sessionId = api_get_session_id();
7326
7327
        // Generates folder
7328
        $result = $this->generate_lp_folder($courseInfo);
7329
        $dir = $result['dir'];
7330
7331
        if (empty($parentId) || '/' == $parentId) {
7332
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
7333
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
7334
7335
            if ('/' === $parentId) {
7336
                $dir = '/';
7337
            }
7338
7339
            // Please, do not modify this dirname formatting.
7340
            if (strstr($dir, '..')) {
7341
                $dir = '/';
7342
            }
7343
7344
            if (!empty($dir[0]) && '.' == $dir[0]) {
7345
                $dir = substr($dir, 1);
7346
            }
7347
            if (!empty($dir[0]) && '/' != $dir[0]) {
7348
                $dir = '/'.$dir;
7349
            }
7350
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
7351
                $dir .= '/';
7352
            }
7353
        } else {
7354
            $parentInfo = DocumentManager::get_document_data_by_id(
7355
                $parentId,
7356
                $courseInfo['code']
7357
            );
7358
            if (!empty($parentInfo)) {
7359
                $dir = $parentInfo['path'].'/';
7360
            }
7361
        }
7362
7363
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7364
7365
        if (!is_dir($filepath)) {
7366
            $dir = '/';
7367
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7368
        }
7369
7370
        $originalTitle = !empty($title) ? $title : $_POST['title'];
7371
7372
        if (!empty($title)) {
7373
            $title = api_replace_dangerous_char(stripslashes($title));
7374
        } else {
7375
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
7376
        }
7377
7378
        $title = disable_dangerous_file($title);
7379
        $filename = $title;
7380
        $content = !empty($content) ? $content : $_POST['content_lp'];
7381
        $tmpFileName = $filename;
7382
7383
        $i = 0;
7384
        while (file_exists($filepath.$tmpFileName.'.html')) {
7385
            $tmpFileName = $filename.'_'.++$i;
7386
        }
7387
7388
        $filename = $tmpFileName.'.html';
7389
        $content = stripslashes($content);
7390
7391
        if (file_exists($filepath.$filename)) {
7392
            return 0;
7393
        }
7394
7395
        $putContent = file_put_contents($filepath.$filename, $content);
7396
7397
        if (false === $putContent) {
7398
            return 0;
7399
        }
7400
7401
        $fileSize = filesize($filepath.$filename);
7402
        $saveFilePath = $dir.$filename;
7403
7404
        $document = DocumentManager::addDocument(
7405
            $courseInfo,
7406
            $saveFilePath,
7407
            'file',
7408
            $fileSize,
7409
            $tmpFileName,
7410
            '',
7411
            0, //readonly
7412
            true,
7413
            null,
7414
            $sessionId,
7415
            $creatorId
7416
        );
7417
7418
        $documentId = $document->getId();
7419
7420
        if (!$document) {
7421
            return 0;
7422
        }
7423
7424
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7425
        $newTitle = $originalTitle;
7426
7427
        if ($newComment || $newTitle) {
7428
            $em = Database::getManager();
7429
7430
            if ($newComment) {
7431
                $document->setComment($newComment);
7432
            }
7433
7434
            if ($newTitle) {
7435
                $document->setTitle($newTitle);
7436
            }
7437
7438
            $em->persist($document);
7439
            $em->flush();
7440
        }
7441
7442
        return $documentId;
7443
    }
7444
7445
    /**
7446
     * Displays the menu for manipulating a step.
7447
     *
7448
     * @return string
7449
     */
7450
    public function displayItemMenu(CLpItem $lpItem)
7451
    {
7452
        $item_id = $lpItem->getIid();
7453
        $audio = $lpItem->getAudio();
7454
        $itemType = $lpItem->getItemType();
7455
        $path = $lpItem->getPath();
7456
7457
        $return = '<div class="actions">';
7458
        $audio_player = null;
7459
        // We display an audio player if needed.
7460
        if (!empty($audio)) {
7461
            /*$webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
7462
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
7463
                .'<audio src="'.$webAudioPath.'" controls>'
7464
                .'</div><br>';*/
7465
        }
7466
7467
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
7468
7469
        if (TOOL_LP_FINAL_ITEM !== $itemType) {
7470
            $return .= Display::url(
7471
                Display::return_icon(
7472
                    'edit.png',
7473
                    get_lang('Edit'),
7474
                    [],
7475
                    ICON_SIZE_SMALL
7476
                ),
7477
                $url.'&action=edit_item&path_item='.$path
7478
            );
7479
7480
            /*$return .= Display::url(
7481
                Display::return_icon(
7482
                    'move.png',
7483
                    get_lang('Move'),
7484
                    [],
7485
                    ICON_SIZE_SMALL
7486
                ),
7487
                $url.'&action=move_item'
7488
            );*/
7489
        }
7490
7491
        // Commented for now as prerequisites cannot be added to chapters.
7492
        if ('dir' !== $itemType) {
7493
            $return .= Display::url(
7494
                Display::return_icon(
7495
                    'accept.png',
7496
                    get_lang('Prerequisites'),
7497
                    [],
7498
                    ICON_SIZE_SMALL
7499
                ),
7500
                $url.'&action=edit_item_prereq'
7501
            );
7502
        }
7503
        $return .= Display::url(
7504
            Display::return_icon(
7505
                'delete.png',
7506
                get_lang('Delete'),
7507
                [],
7508
                ICON_SIZE_SMALL
7509
            ),
7510
            $url.'&action=delete_item'
7511
        );
7512
7513
        /*if (in_array($itemType, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
7514
            $documentData = DocumentManager::get_document_data_by_id($path, $course_code);
7515
            if (empty($documentData)) {
7516
                // Try with iid
7517
                $table = Database::get_course_table(TABLE_DOCUMENT);
7518
                $sql = "SELECT path FROM $table
7519
                        WHERE
7520
                              c_id = ".api_get_course_int_id()." AND
7521
                              iid = ".$path." AND
7522
                              path NOT LIKE '%_DELETED_%'";
7523
                $result = Database::query($sql);
7524
                $documentData = Database::fetch_array($result);
7525
                if ($documentData) {
7526
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
7527
                }
7528
            }
7529
            if (isset($documentData['absolute_path_from_document'])) {
7530
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
7531
            }
7532
        }*/
7533
7534
        $return .= '</div>';
7535
7536
        if (!empty($audio_player)) {
7537
            $return .= $audio_player;
7538
        }
7539
7540
        return $return;
7541
    }
7542
7543
    /**
7544
     * Creates the javascript needed for filling up the checkboxes without page reload.
7545
     *
7546
     * @return string
7547
     */
7548
    public function get_js_dropdown_array()
7549
    {
7550
        $course_id = api_get_course_int_id();
7551
        $return = 'var child_name = new Array();'."\n";
7552
        $return .= 'var child_value = new Array();'."\n\n";
7553
        $return .= 'child_name[0] = new Array();'."\n";
7554
        $return .= 'child_value[0] = new Array();'."\n\n";
7555
7556
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7557
        $sql = "SELECT * FROM ".$tbl_lp_item."
7558
                WHERE
7559
                    c_id = $course_id AND
7560
                    lp_id = ".$this->lp_id." AND
7561
                    parent_item_id = 0
7562
                ORDER BY display_order ASC";
7563
        Database::query($sql);
7564
        $i = 0;
7565
7566
        $list = $this->getItemsForForm(true);
7567
7568
        foreach ($list as $row_zero) {
7569
            if (TOOL_LP_FINAL_ITEM !== $row_zero['item_type']) {
7570
                if (TOOL_QUIZ == $row_zero['item_type']) {
7571
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
7572
                }
7573
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
7574
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
7575
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
7576
            }
7577
        }
7578
7579
        $return .= "\n";
7580
        $sql = "SELECT * FROM $tbl_lp_item
7581
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7582
        $res = Database::query($sql);
7583
        while ($row = Database::fetch_array($res)) {
7584
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
7585
                           WHERE
7586
                                c_id = ".$course_id." AND
7587
                                parent_item_id = ".$row['iid']."
7588
                           ORDER BY display_order ASC";
7589
            $res_parent = Database::query($sql_parent);
7590
            $i = 0;
7591
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
7592
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
7593
7594
            while ($row_parent = Database::fetch_array($res_parent)) {
7595
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
7596
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
7597
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
7598
            }
7599
            $return .= "\n";
7600
        }
7601
7602
        $return .= "
7603
            function load_cbo(id) {
7604
                if (!id) {
7605
                    return false;
7606
                }
7607
7608
                var cbo = document.getElementById('previous');
7609
                for(var i = cbo.length - 1; i > 0; i--) {
7610
                    cbo.options[i] = null;
7611
                }
7612
7613
                var k=0;
7614
                for(var i = 1; i <= child_name[id].length; i++){
7615
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
7616
                    option.style.paddingLeft = '40px';
7617
                    cbo.options[i] = option;
7618
                    k = i;
7619
                }
7620
7621
                cbo.options[k].selected = true;
7622
                //$('#previous').selectpicker('refresh');
7623
            }";
7624
7625
        return $return;
7626
    }
7627
7628
    /**
7629
     * Display the form to allow moving an item.
7630
     *
7631
     * @param CLpItem $lpItem
7632
     *
7633
     * @throws Exception
7634
     * @throws HTML_QuickForm_Error
7635
     *
7636
     * @return string HTML form
7637
     */
7638
    public function display_move_item($lpItem)
7639
    {
7640
        $return = '';
7641
        $path = $lpItem->getPath();
7642
7643
        if ($lpItem) {
7644
            $itemType = $lpItem->getItemType();
7645
            switch ($itemType) {
7646
                case 'dir':
7647
                case 'asset':
7648
                    $return .= $this->displayItemMenu($lpItem);
7649
                    $return .= $this->display_item_form(
7650
                        $lpItem,
7651
                        get_lang('Move the current section'),
7652
                        'move',
7653
                        $row
7654
                    );
7655
                    break;
7656
                case TOOL_DOCUMENT:
7657
                    $return .= $this->displayItemMenu($lpItem);
7658
                    $return .= $this->displayDocumentForm('move', $lpItem);
7659
                    break;
7660
                case TOOL_LINK:
7661
                    $link = null;
7662
                    if (!empty($path)) {
7663
                        $repo = Container::getLinkRepository();
7664
                        $link = $repo->find($path);
7665
                    }
7666
                    $return .= $this->displayItemMenu($lpItem);
7667
                    $return .= $this->display_link_form('move', $lpItem, $link);
7668
                    break;
7669
                case TOOL_HOTPOTATOES:
7670
                    $return .= $this->displayItemMenu($lpItem);
7671
                    $return .= $this->display_link_form('move', $lpItem, $row);
7672
                    break;
7673
                case TOOL_QUIZ:
7674
                    $return .= $this->displayItemMenu($lpItem);
7675
                    $return .= $this->display_quiz_form('move', $lpItem, $row);
7676
                    break;
7677
                case TOOL_STUDENTPUBLICATION:
7678
                    $return .= $this->displayItemMenu($lpItem);
7679
                    $return .= $this->display_student_publication_form('move', $lpItem, $row);
7680
                    break;
7681
                case TOOL_FORUM:
7682
                    $return .= $this->displayItemMenu($lpItem);
7683
                    $return .= $this->display_forum_form('move', $lpItem, $row);
7684
                    break;
7685
                case TOOL_THREAD:
7686
                    $return .= $this->displayItemMenu($lpItem);
7687
                    $return .= $this->display_forum_form('move', $lpItem, $row);
7688
                    break;
7689
            }
7690
        }
7691
7692
        return $return;
7693
    }
7694
7695
    /**
7696
     * Return HTML form to allow prerequisites selection.
7697
     *
7698
     * @todo use FormValidator
7699
     *
7700
     * @return string HTML form
7701
     */
7702
    public function display_item_prerequisites_form(CLpItem $lpItem)
7703
    {
7704
        $course_id = api_get_course_int_id();
7705
        $prerequisiteId = $lpItem->getPrerequisite();
7706
        $itemId = $lpItem->getIid();
7707
7708
        $return = '<legend>';
7709
        $return .= get_lang('Add/edit prerequisites');
7710
        $return .= '</legend>';
7711
        $return .= '<form method="POST">';
7712
        $return .= '<div class="table-responsive">';
7713
        $return .= '<table class="table table-hover">';
7714
        $return .= '<thead>';
7715
        $return .= '<tr>';
7716
        $return .= '<th>'.get_lang('Prerequisites').'</th>';
7717
        $return .= '<th width="140">'.get_lang('minimum').'</th>';
7718
        $return .= '<th width="140">'.get_lang('maximum').'</th>';
7719
        $return .= '</tr>';
7720
        $return .= '</thead>';
7721
7722
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
7723
        $return .= '<tbody>';
7724
        $return .= '<tr>';
7725
        $return .= '<td colspan="3">';
7726
        $return .= '<div class="radio learnpath"><label for="idnone">';
7727
        $return .= '<input checked="checked" id="idnone" name="prerequisites" type="radio" />';
7728
        $return .= get_lang('none').'</label>';
7729
        $return .= '</div>';
7730
        $return .= '</tr>';
7731
7732
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7733
        $sql = "SELECT * FROM $tbl_lp_item
7734
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7735
        $result = Database::query($sql);
7736
7737
        $selectedMinScore = [];
7738
        $selectedMaxScore = [];
7739
        $masteryScore = [];
7740
        while ($row = Database::fetch_array($result)) {
7741
            if ($row['iid'] == $itemId) {
7742
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
7743
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
7744
            }
7745
            $masteryScore[$row['iid']] = $row['mastery_score'];
7746
        }
7747
7748
        $arrLP = $this->getItemsForForm();
7749
        $this->tree_array($arrLP);
7750
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7751
        unset($this->arrMenu);
7752
7753
        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...
7754
            $item = $arrLP[$i];
7755
7756
            if ($item['id'] == $itemId) {
7757
                break;
7758
            }
7759
7760
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
7761
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
7762
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
7763
7764
            $return .= '<tr>';
7765
            $return .= '<td '.((TOOL_QUIZ != $item['item_type'] && TOOL_HOTPOTATOES != $item['item_type']) ? ' colspan="3"' : '').'>';
7766
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
7767
            $return .= '<label for="id'.$item['id'].'">';
7768
7769
            $checked = '';
7770
            if (null !== $prerequisiteId) {
7771
                $checked = in_array($prerequisiteId, [$item['id'], $item['ref']]) ? ' checked="checked" ' : '';
7772
            }
7773
7774
            $disabled = 'dir' === $item['item_type'] ? ' disabled="disabled" ' : '';
7775
7776
            $return .= '<input '.$checked.' '.$disabled.' id="id'.$item['id'].'" name="prerequisites" type="radio" value="'.$item['id'].'" />';
7777
7778
            $icon_name = str_replace(' ', '', $item['item_type']);
7779
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
7780
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
7781
            } else {
7782
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
7783
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
7784
                } else {
7785
                    $return .= Display::return_icon('folder_document.png');
7786
                }
7787
            }
7788
7789
            $return .= $item['title'].'</label>';
7790
            $return .= '</div>';
7791
            $return .= '</td>';
7792
7793
            if (TOOL_QUIZ == $item['item_type']) {
7794
                // lets update max_score Tests information depending of the Tests Advanced properties
7795
                $lpItemObj = new LpItem($course_id, $item['id']);
7796
                $exercise = new Exercise($course_id);
7797
                $exercise->read($lpItemObj->path);
7798
                $lpItemObj->max_score = $exercise->get_max_score();
7799
                $lpItemObj->update();
7800
                $item['max_score'] = $lpItemObj->max_score;
7801
7802
                if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
7803
                    // Backwards compatibility with 1.9.x use mastery_score as min value
7804
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
7805
                }
7806
7807
                $return .= '<td>';
7808
                $return .= '<input
7809
                    class="form-control"
7810
                    size="4" maxlength="3"
7811
                    name="min_'.$item['id'].'"
7812
                    type="number"
7813
                    min="0"
7814
                    step="any"
7815
                    max="'.$item['max_score'].'"
7816
                    value="'.$selectedMinScoreValue.'"
7817
                />';
7818
                $return .= '</td>';
7819
                $return .= '<td>';
7820
                $return .= '<input
7821
                    class="form-control"
7822
                    size="4"
7823
                    maxlength="3"
7824
                    name="max_'.$item['id'].'"
7825
                    type="number"
7826
                    min="0"
7827
                    step="any"
7828
                    max="'.$item['max_score'].'"
7829
                    value="'.$selectedMaxScoreValue.'"
7830
                />';
7831
                $return .= '</td>';
7832
            }
7833
7834
            if (TOOL_HOTPOTATOES == $item['item_type']) {
7835
                $return .= '<td>';
7836
                $return .= '<input
7837
                    size="4"
7838
                    maxlength="3"
7839
                    name="min_'.$item['id'].'"
7840
                    type="number"
7841
                    min="0"
7842
                    step="any"
7843
                    max="'.$item['max_score'].'"
7844
                    value="'.$selectedMinScoreValue.'"
7845
                />';
7846
                $return .= '</td>';
7847
                $return .= '<td>';
7848
                $return .= '<input
7849
                    size="4"
7850
                    maxlength="3"
7851
                    name="max_'.$item['id'].'"
7852
                    type="number"
7853
                    min="0"
7854
                    step="any"
7855
                    max="'.$item['max_score'].'"
7856
                    value="'.$selectedMaxScoreValue.'"
7857
                />';
7858
                $return .= '</td>';
7859
            }
7860
            $return .= '</tr>';
7861
        }
7862
        $return .= '<tr>';
7863
        $return .= '</tr>';
7864
        $return .= '</tbody>';
7865
        $return .= '</table>';
7866
        $return .= '</div>';
7867
        $return .= '<div class="form-group">';
7868
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
7869
            get_lang('Save prerequisites settings').'</button>';
7870
        $return .= '</form>';
7871
7872
        return $return;
7873
    }
7874
7875
    /**
7876
     * Return HTML list to allow prerequisites selection for lp.
7877
     *
7878
     * @return string HTML form
7879
     */
7880
    public function display_lp_prerequisites_list()
7881
    {
7882
        $course_id = api_get_course_int_id();
7883
        $lp_id = $this->lp_id;
7884
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
7885
7886
        // get current prerequisite
7887
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
7888
        $result = Database::query($sql);
7889
        $row = Database::fetch_array($result);
7890
        $prerequisiteId = $row['prerequisite'];
7891
        $session_id = api_get_session_id();
7892
        $session_condition = api_get_session_condition($session_id, true, true);
7893
        $sql = "SELECT * FROM $tbl_lp
7894
                WHERE c_id = $course_id $session_condition
7895
                ORDER BY display_order ";
7896
        $rs = Database::query($sql);
7897
        $return = '';
7898
        $return .= '<select name="prerequisites" class="form-control">';
7899
        $return .= '<option value="0">'.get_lang('none').'</option>';
7900
        if (Database::num_rows($rs) > 0) {
7901
            while ($row = Database::fetch_array($rs)) {
7902
                if ($row['iid'] == $lp_id) {
7903
                    continue;
7904
                }
7905
                $return .= '<option
7906
                    value="'.$row['iid'].'" '.(($row['iid'] == $prerequisiteId) ? ' selected ' : '').'>'.
7907
                    $row['name'].
7908
                    '</option>';
7909
            }
7910
        }
7911
        $return .= '</select>';
7912
7913
        return $return;
7914
    }
7915
7916
    /**
7917
     * Creates a list with all the documents in it.
7918
     *
7919
     * @param bool $showInvisibleFiles
7920
     *
7921
     * @throws Exception
7922
     * @throws HTML_QuickForm_Error
7923
     *
7924
     * @return string
7925
     */
7926
    public function get_documents($showInvisibleFiles = false)
7927
    {
7928
        $course_info = api_get_course_info();
7929
        $sessionId = api_get_session_id();
7930
        $documentTree = DocumentManager::get_document_preview(
7931
            $course_info,
7932
            $this->lp_id,
7933
            null,
7934
            $sessionId,
7935
            true,
7936
            null,
7937
            null,
7938
            $showInvisibleFiles,
7939
            true
7940
        );
7941
7942
        $headers = [
7943
            get_lang('Files'),
7944
            get_lang('CreateTheDocument'),
7945
            get_lang('CreateReadOutText'),
7946
            get_lang('Upload'),
7947
        ];
7948
7949
        $form = new FormValidator(
7950
            'form_upload',
7951
            'POST',
7952
            $this->getCurrentBuildingModeURL(),
7953
            '',
7954
            ['enctype' => 'multipart/form-data']
7955
        );
7956
7957
        $folders = DocumentManager::get_all_document_folders(
7958
            api_get_course_info(),
7959
            0,
7960
            true
7961
        );
7962
7963
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
7964
7965
        DocumentManager::build_directory_selector(
7966
            $folders,
7967
            $lpPathInfo['id'],
7968
            [],
7969
            true,
7970
            $form,
7971
            'directory_parent_id'
7972
        );
7973
7974
        $group = [
7975
            $form->createElement(
7976
                'radio',
7977
                'if_exists',
7978
                get_lang('If file exists:'),
7979
                get_lang('Do nothing'),
7980
                'nothing'
7981
            ),
7982
            $form->createElement(
7983
                'radio',
7984
                'if_exists',
7985
                null,
7986
                get_lang('Overwrite the existing file'),
7987
                'overwrite'
7988
            ),
7989
            $form->createElement(
7990
                'radio',
7991
                'if_exists',
7992
                null,
7993
                get_lang('Rename the uploaded file if it exists'),
7994
                'rename'
7995
            ),
7996
        ];
7997
        $form->addGroup($group, null, get_lang('If file exists:'));
7998
7999
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
8000
        $defaultFileExistsOption = 'rename';
8001
        if (!empty($fileExistsOption)) {
8002
            $defaultFileExistsOption = $fileExistsOption;
8003
        }
8004
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
8005
8006
        // Check box options
8007
        $form->addElement(
8008
            'checkbox',
8009
            'unzip',
8010
            get_lang('Options'),
8011
            get_lang('Uncompress zip')
8012
        );
8013
8014
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
8015
        $form->addMultipleUpload($url);
8016
8017
        $lpItem = new CLpItem();
8018
        $lpItem->setItemType(TOOL_DOCUMENT);
8019
        $new = $this->displayDocumentForm('add', $lpItem);
8020
8021
        /*$lpItem = new CLpItem();
8022
        $lpItem->setItemType(TOOL_READOUT_TEXT);
8023
        $frmReadOutText = $this->displayDocumentForm('add');*/
8024
8025
        $headers = [
8026
            get_lang('Files'),
8027
            get_lang('Create a new document'),
8028
            get_lang('Create read-out text'),
8029
            get_lang('Upload'),
8030
        ];
8031
8032
        return Display::tabs(
8033
            $headers,
8034
            [$documentTree, $new, $form->returnForm()],
8035
            'subtab'
8036
        );
8037
    }
8038
8039
    /**
8040
     * Creates a list with all the exercises (quiz) in it.
8041
     *
8042
     * @return string
8043
     */
8044
    public function get_exercises()
8045
    {
8046
        $course_id = api_get_course_int_id();
8047
        $session_id = api_get_session_id();
8048
        $userInfo = api_get_user_info();
8049
8050
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
8051
        $condition_session = api_get_session_condition($session_id, true, true);
8052
        $setting = 'true' === api_get_setting('lp.show_invisible_exercise_in_lp_toc');
8053
8054
        $activeCondition = ' active <> -1 ';
8055
        if ($setting) {
8056
            $activeCondition = ' active = 1 ';
8057
        }
8058
8059
        $categoryCondition = '';
8060
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
8061
        if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
8062
            $categoryCondition = " AND exercise_category_id = $categoryId ";
8063
        }
8064
8065
        $keywordCondition = '';
8066
        $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
8067
8068
        if (!empty($keyword)) {
8069
            $keyword = Database::escape_string($keyword);
8070
            $keywordCondition = " AND title LIKE '%$keyword%' ";
8071
        }
8072
8073
        $sql_quiz = "SELECT * FROM $tbl_quiz
8074
                     WHERE
8075
                            c_id = $course_id AND
8076
                            $activeCondition
8077
                            $condition_session
8078
                            $categoryCondition
8079
                            $keywordCondition
8080
                     ORDER BY title ASC";
8081
        $res_quiz = Database::query($sql_quiz);
8082
8083
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
8084
8085
        // Create a search-box
8086
        $form = new FormValidator('search_simple', 'get', $currentUrl);
8087
        $form->addHidden('action', 'add_item');
8088
        $form->addHidden('type', 'step');
8089
        $form->addHidden('lp_id', $this->lp_id);
8090
        $form->addHidden('lp_build_selected', '2');
8091
8092
        $form->addCourseHiddenParams();
8093
        $form->addText(
8094
            'keyword',
8095
            get_lang('Search'),
8096
            false,
8097
            [
8098
                'aria-label' => get_lang('Search'),
8099
            ]
8100
        );
8101
8102
        if (api_get_configuration_value('allow_exercise_categories')) {
8103
            $manager = new ExerciseCategoryManager();
8104
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
8105
            if (!empty($options)) {
8106
                $form->addSelect(
8107
                    'category_id',
8108
                    get_lang('Category'),
8109
                    $options,
8110
                    ['placeholder' => get_lang('Please select an option')]
8111
                );
8112
            }
8113
        }
8114
8115
        $form->addButtonSearch(get_lang('Search'));
8116
        $return = $form->returnForm();
8117
8118
        $return .= '<ul class="lp_resource">';
8119
        $return .= '<li class="lp_resource_element">';
8120
        $return .= Display::return_icon('new_exercice.png');
8121
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
8122
            get_lang('New test').'</a>';
8123
        $return .= '</li>';
8124
8125
        $previewIcon = Display::return_icon(
8126
            'preview_view.png',
8127
            get_lang('Preview')
8128
        );
8129
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
8130
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8131
8132
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
8133
        $repo = Container::getQuizRepository();
8134
        $courseEntity = api_get_course_entity();
8135
        $sessionEntity = api_get_session_entity();
8136
        while ($row_quiz = Database::fetch_array($res_quiz)) {
8137
            $exerciseId = $row_quiz['iid'];
8138
            /** @var CQuiz $exercise */
8139
            $exercise = $repo->find($exerciseId);
8140
            $title = strip_tags(api_html_entity_decode($row_quiz['title']));
8141
8142
            $visibility = $exercise->isVisible($courseEntity, $sessionEntity);
8143
            /*$visibility = api_get_item_visibility(
8144
                ['real_id' => $course_id],
8145
                TOOL_QUIZ,
8146
                $row_quiz['iid'],
8147
                $session_id
8148
            );*/
8149
8150
            $link = Display::url(
8151
                $previewIcon,
8152
                $exerciseUrl.'&exerciseId='.$exerciseId,
8153
                ['target' => '_blank']
8154
            );
8155
            $return .= '<li class="lp_resource_element" data_id="'.$exerciseId.'" data_type="quiz" title="'.$title.'" >';
8156
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
8157
            $return .= $quizIcon;
8158
            $sessionStar = api_get_session_image(
8159
                $row_quiz['session_id'],
8160
                $userInfo['status']
8161
            );
8162
            $return .= Display::url(
8163
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
8164
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$exerciseId.'&lp_id='.$this->lp_id,
8165
                [
8166
                    'class' => false === $visibility ? 'moved text-muted' : 'moved',
8167
                ]
8168
            );
8169
            $return .= '</li>';
8170
        }
8171
8172
        $return .= '</ul>';
8173
8174
        return $return;
8175
    }
8176
8177
    /**
8178
     * Creates a list with all the links in it.
8179
     *
8180
     * @return string
8181
     */
8182
    public function get_links()
8183
    {
8184
        $sessionId = api_get_session_id();
8185
        $repo = Container::getLinkRepository();
8186
8187
        $course = api_get_course_entity();
8188
        $session = api_get_session_entity($sessionId);
8189
        $qb = $repo->getResourcesByCourse($course, $session);
8190
        /** @var CLink[] $links */
8191
        $links = $qb->getQuery()->getResult();
8192
8193
        $selfUrl = api_get_self();
8194
        $courseIdReq = api_get_cidreq();
8195
        $userInfo = api_get_user_info();
8196
8197
        $moveEverywhereIcon = Display::return_icon(
8198
            'move_everywhere.png',
8199
            get_lang('Move'),
8200
            [],
8201
            ICON_SIZE_TINY
8202
        );
8203
8204
        /*$condition_session = api_get_session_condition(
8205
            $session_id,
8206
            true,
8207
            true,
8208
            'link.session_id'
8209
        );
8210
8211
        $sql = "SELECT
8212
                    link.id as link_id,
8213
                    link.title as link_title,
8214
                    link.session_id as link_session_id,
8215
                    link.category_id as category_id,
8216
                    link_category.category_title as category_title
8217
                FROM $tbl_link as link
8218
                LEFT JOIN $linkCategoryTable as link_category
8219
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
8220
                WHERE link.c_id = $course_id $condition_session
8221
                ORDER BY link_category.category_title ASC, link.title ASC";
8222
        $result = Database::query($sql);*/
8223
        $categorizedLinks = [];
8224
        $categories = [];
8225
8226
        foreach ($links as $link) {
8227
            $categoryId = null !== $link->getCategory() ? $link->getCategory()->getIid() : 0;
8228
8229
            if (empty($categoryId)) {
8230
                $categories[0] = get_lang('Uncategorized');
8231
            } else {
8232
                $category = $link->getCategory();
8233
                $categories[$categoryId] = $category->getCategoryTitle();
8234
            }
8235
            $categorizedLinks[$categoryId][$link->getIid()] = $link;
8236
        }
8237
8238
        $linksHtmlCode =
8239
            '<script>
8240
            function toggle_tool(tool, id) {
8241
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
8242
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
8243
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
8244
                } else {
8245
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
8246
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
8247
                }
8248
            }
8249
        </script>
8250
8251
        <ul class="lp_resource">
8252
            <li class="lp_resource_element">
8253
                '.Display::return_icon('linksnew.gif').'
8254
                <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').'">'.
8255
                get_lang('Add a link').'
8256
                </a>
8257
            </li>';
8258
8259
        foreach ($categorizedLinks as $categoryId => $links) {
8260
            $linkNodes = null;
8261
            /** @var CLink $link */
8262
            foreach ($links as $key => $link) {
8263
                $title = $link->getTitle();
8264
8265
                $linkUrl = Display::url(
8266
                    Display::return_icon('preview_view.png', get_lang('Preview')),
8267
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
8268
                    ['target' => '_blank']
8269
                );
8270
8271
                if ($link->isVisible($course, $session)) {
8272
                    //$sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
8273
                    $sessionStar = '';
8274
                    $linkNodes .=
8275
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
8276
                        <a class="moved" href="#">'.
8277
                            $moveEverywhereIcon.
8278
                        '</a>
8279
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
8280
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
8281
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
8282
                        Security::remove_XSS($title).$sessionStar.$linkUrl.
8283
                        '</a>
8284
                    </li>';
8285
                }
8286
            }
8287
            $linksHtmlCode .=
8288
                '<li>
8289
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
8290
                    <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
8291
                    align="absbottom" />
8292
                </a>
8293
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
8294
            </li>
8295
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
8296
        }
8297
        $linksHtmlCode .= '</ul>';
8298
8299
        return $linksHtmlCode;
8300
    }
8301
8302
    /**
8303
     * Creates a list with all the student publications in it.
8304
     *
8305
     * @return string
8306
     */
8307
    public function get_student_publications()
8308
    {
8309
        $return = '<ul class="lp_resource">';
8310
        $return .= '<li class="lp_resource_element">';
8311
        /*
8312
        $return .= Display::return_icon('works_new.gif');
8313
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
8314
            get_lang('Add the Assignments tool to the course').'</a>';
8315
        $return .= '</li>';*/
8316
8317
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
8318
        $works = getWorkListTeacher(0, 100, null, null, null);
8319
        if (!empty($works)) {
8320
            foreach ($works as $work) {
8321
                $link = Display::url(
8322
                    Display::return_icon('preview_view.png', get_lang('Preview')),
8323
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
8324
                    ['target' => '_blank']
8325
                );
8326
8327
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
8328
                $return .= '<a class="moved" href="#">';
8329
                $return .= Display::return_icon(
8330
                    'move_everywhere.png',
8331
                    get_lang('Move'),
8332
                    [],
8333
                    ICON_SIZE_TINY
8334
                );
8335
                $return .= '</a> ';
8336
8337
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
8338
                $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.'">'.
8339
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
8340
                </a>';
8341
8342
                $return .= '</li>';
8343
            }
8344
        }
8345
8346
        $return .= '</ul>';
8347
8348
        return $return;
8349
    }
8350
8351
    /**
8352
     * Creates a list with all the forums in it.
8353
     *
8354
     * @return string
8355
     */
8356
    public function get_forums()
8357
    {
8358
        require_once '../forum/forumfunction.inc.php';
8359
8360
        $forumCategories = get_forum_categories();
8361
        $forumsInNoCategory = get_forums_in_category(0);
8362
        if (!empty($forumsInNoCategory)) {
8363
            $forumCategories = array_merge(
8364
                $forumCategories,
8365
                [
8366
                    [
8367
                        'cat_id' => 0,
8368
                        'session_id' => 0,
8369
                        'visibility' => 1,
8370
                        'cat_comment' => null,
8371
                    ],
8372
                ]
8373
            );
8374
        }
8375
8376
        $a_forums = [];
8377
        $courseEntity = api_get_course_entity(api_get_course_int_id());
8378
        $sessionEntity = api_get_session_entity(api_get_session_id());
8379
8380
        foreach ($forumCategories as $forumCategory) {
8381
            // The forums in this category.
8382
            $forumsInCategory = get_forums_in_category($forumCategory->getIid());
8383
            if (!empty($forumsInCategory)) {
8384
                foreach ($forumsInCategory as $forum) {
8385
                    if ($forum->isVisible($courseEntity, $sessionEntity)) {
8386
                        $a_forums[] = $forum;
8387
                    }
8388
                }
8389
            }
8390
        }
8391
8392
        $return = '<ul class="lp_resource">';
8393
8394
        // First add link
8395
        $return .= '<li class="lp_resource_element">';
8396
        $return .= Display::return_icon('new_forum.png');
8397
        $return .= Display::url(
8398
            get_lang('Create a new forum'),
8399
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
8400
                'action' => 'add',
8401
                'content' => 'forum',
8402
                'lp_id' => $this->lp_id,
8403
            ]),
8404
            ['title' => get_lang('Create a new forum')]
8405
        );
8406
        $return .= '</li>';
8407
8408
        $return .= '<script>
8409
            function toggle_forum(forum_id) {
8410
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
8411
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
8412
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
8413
                } else {
8414
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
8415
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
8416
                }
8417
            }
8418
        </script>';
8419
8420
        foreach ($a_forums as $forum) {
8421
            $forumId = $forum->getIid();
8422
            $title = Security::remove_XSS($forum->getForumTitle());
8423
            $link = Display::url(
8424
                Display::return_icon('preview_view.png', get_lang('Preview')),
8425
                api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forumId,
8426
                ['target' => '_blank']
8427
            );
8428
8429
            $return .= '<li class="lp_resource_element" data_id="'.$forumId.'" data_type="'.TOOL_FORUM.'" title="'.$title.'" >';
8430
            $return .= '<a class="moved" href="#">';
8431
            $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8432
            $return .= ' </a>';
8433
            $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
8434
            $return .= '<a onclick="javascript:toggle_forum('.$forumId.');" style="cursor:hand; vertical-align:middle">
8435
                            <img src="'.Display::returnIconPath('add.png').'" id="forum_'.$forumId.'_opener" align="absbottom" />
8436
                        </a>
8437
                        <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">'.
8438
                $title.' '.$link.'</a>';
8439
8440
            $return .= '</li>';
8441
8442
            $return .= '<div style="display:none" id="forum_'.$forumId.'_content">';
8443
            $threads = get_threads($forumId);
8444
            if (is_array($threads)) {
8445
                foreach ($threads as $thread) {
8446
                    $threadId = $thread->getIid();
8447
                    $link = Display::url(
8448
                        Display::return_icon('preview_view.png', get_lang('Preview')),
8449
                        api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forumId.'&thread='.$threadId,
8450
                        ['target' => '_blank']
8451
                    );
8452
8453
                    $return .= '<li class="lp_resource_element" data_id="'.$thread->getIid().'" data_type="'.TOOL_THREAD.'" title="'.$thread->getThreadTitle().'" >';
8454
                    $return .= '&nbsp;<a class="moved" href="#">';
8455
                    $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8456
                    $return .= ' </a>';
8457
                    $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
8458
                    $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.'">'.
8459
                        Security::remove_XSS($thread->getThreadTitle()).' '.$link.'</a>';
8460
                    $return .= '</li>';
8461
                }
8462
            }
8463
            $return .= '</div>';
8464
        }
8465
        $return .= '</ul>';
8466
8467
        return $return;
8468
    }
8469
8470
    /**
8471
     * // TODO: The output encoding should be equal to the system encoding.
8472
     *
8473
     * Exports the learning path as a SCORM package. This is the main function that
8474
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
8475
     * whole thing and returns the zip.
8476
     *
8477
     * This method needs to be called in PHP5, as it will fail with non-adequate
8478
     * XML package (like the ones for PHP4), and it is *not* a static method, so
8479
     * you need to call it on a learnpath object.
8480
     *
8481
     * @TODO The method might be redefined later on in the scorm class itself to avoid
8482
     * creating a SCORM structure if there is one already. However, if the initial SCORM
8483
     * path has been modified, it should use the generic method here below.
8484
     *
8485
     * @return string Returns the zip package string, or null if error
8486
     */
8487
    public function scormExport()
8488
    {
8489
        api_set_more_memory_and_time_limits();
8490
8491
        $_course = api_get_course_info();
8492
        $course_id = $_course['real_id'];
8493
        // Create the zip handler (this will remain available throughout the method).
8494
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
8495
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
8496
        $temp_dir_short = uniqid('scorm_export', true);
8497
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
8498
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
8499
        $zip_folder = new PclZip($temp_zip_file);
8500
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
8501
        $root_path = $main_path = api_get_path(SYS_PATH);
8502
        $files_cleanup = [];
8503
8504
        // Place to temporarily stash the zip file.
8505
        // create the temp dir if it doesn't exist
8506
        // or do a cleanup before creating the zip file.
8507
        if (!is_dir($temp_zip_dir)) {
8508
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
8509
        } else {
8510
            // Cleanup: Check the temp dir for old files and delete them.
8511
            $handle = opendir($temp_zip_dir);
8512
            while (false !== ($file = readdir($handle))) {
8513
                if ('.' != $file && '..' != $file) {
8514
                    unlink("$temp_zip_dir/$file");
8515
                }
8516
            }
8517
            closedir($handle);
8518
        }
8519
        $zip_files = $zip_files_abs = $zip_files_dist = [];
8520
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
8521
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
8522
        ) {
8523
            // Remove the possible . at the end of the path.
8524
            $dest_path_to_lp = '.' == substr($this->path, -1) ? substr($this->path, 0, -1) : $this->path;
8525
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
8526
            mkdir(
8527
                $dest_path_to_scorm_folder,
8528
                api_get_permissions_for_new_directories(),
8529
                true
8530
            );
8531
            copyr(
8532
                $current_course_path.'/scorm/'.$this->path,
8533
                $dest_path_to_scorm_folder,
8534
                ['imsmanifest'],
8535
                $zip_files
8536
            );
8537
        }
8538
8539
        // Build a dummy imsmanifest structure.
8540
        // Do not add to the zip yet (we still need it).
8541
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
8542
        // Aggregation Model official document, section "2.3 Content Packaging".
8543
        // We are going to build a UTF-8 encoded manifest.
8544
        // Later we will recode it to the desired (and supported) encoding.
8545
        $xmldoc = new DOMDocument('1.0');
8546
        $root = $xmldoc->createElement('manifest');
8547
        $root->setAttribute('identifier', 'SingleCourseManifest');
8548
        $root->setAttribute('version', '1.1');
8549
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
8550
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
8551
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
8552
        $root->setAttribute(
8553
            'xsi:schemaLocation',
8554
            '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'
8555
        );
8556
        // Build mandatory sub-root container elements.
8557
        $metadata = $xmldoc->createElement('metadata');
8558
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
8559
        $metadata->appendChild($md_schema);
8560
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
8561
        $metadata->appendChild($md_schemaversion);
8562
        $root->appendChild($metadata);
8563
8564
        $organizations = $xmldoc->createElement('organizations');
8565
        $resources = $xmldoc->createElement('resources');
8566
8567
        // Build the only organization we will use in building our learnpaths.
8568
        $organizations->setAttribute('default', 'chamilo_scorm_export');
8569
        $organization = $xmldoc->createElement('organization');
8570
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
8571
        // To set the title of the SCORM entity (=organization), we take the name given
8572
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
8573
        // learning path charset) as it is the encoding that defines how it is stored
8574
        // in the database. Then we convert it to HTML entities again as the "&" character
8575
        // alone is not authorized in XML (must be &amp;).
8576
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
8577
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
8578
        $organization->appendChild($org_title);
8579
        $folder_name = 'document';
8580
8581
        // Removes the learning_path/scorm_folder path when exporting see #4841
8582
        $path_to_remove = '';
8583
        $path_to_replace = '';
8584
        $result = $this->generate_lp_folder($_course);
8585
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
8586
            $path_to_remove = 'document'.$result['dir'];
8587
            $path_to_replace = $folder_name.'/';
8588
        }
8589
8590
        // Fixes chamilo scorm exports
8591
        if ('chamilo_scorm_export' === $this->ref) {
8592
            $path_to_remove = 'scorm/'.$this->path.'/document/';
8593
        }
8594
8595
        // For each element, add it to the imsmanifest structure, then add it to the zip.
8596
        $link_updates = [];
8597
        $links_to_create = [];
8598
        foreach ($this->ordered_items as $index => $itemId) {
8599
            /** @var learnpathItem $item */
8600
            $item = $this->items[$itemId];
8601
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
8602
                // Get included documents from this item.
8603
                if ('sco' === $item->type) {
8604
                    $inc_docs = $item->get_resources_from_source(
8605
                        null,
8606
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
8607
                    );
8608
                } else {
8609
                    $inc_docs = $item->get_resources_from_source();
8610
                }
8611
8612
                // Give a child element <item> to the <organization> element.
8613
                $my_item_id = $item->get_id();
8614
                $my_item = $xmldoc->createElement('item');
8615
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
8616
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
8617
                $my_item->setAttribute('isvisible', 'true');
8618
                // Give a child element <title> to the <item> element.
8619
                $my_title = $xmldoc->createElement(
8620
                    'title',
8621
                    htmlspecialchars(
8622
                        api_utf8_encode($item->get_title()),
8623
                        ENT_QUOTES,
8624
                        'UTF-8'
8625
                    )
8626
                );
8627
                $my_item->appendChild($my_title);
8628
                // Give a child element <adlcp:prerequisites> to the <item> element.
8629
                $my_prereqs = $xmldoc->createElement(
8630
                    'adlcp:prerequisites',
8631
                    $this->get_scorm_prereq_string($my_item_id)
8632
                );
8633
                $my_prereqs->setAttribute('type', 'aicc_script');
8634
                $my_item->appendChild($my_prereqs);
8635
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
8636
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
8637
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
8638
                //$xmldoc->createElement('adlcp:timelimitaction','');
8639
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
8640
                //$xmldoc->createElement('adlcp:datafromlms','');
8641
                // Give a child element <adlcp:masteryscore> to the <item> element.
8642
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
8643
                $my_item->appendChild($my_masteryscore);
8644
8645
                // Attach this item to the organization element or hits parent if there is one.
8646
                if (!empty($item->parent) && 0 != $item->parent) {
8647
                    $children = $organization->childNodes;
8648
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
8649
                    if (is_object($possible_parent)) {
8650
                        $possible_parent->appendChild($my_item);
8651
                    } else {
8652
                        if ($this->debug > 0) {
8653
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
8654
                        }
8655
                    }
8656
                } else {
8657
                    if ($this->debug > 0) {
8658
                        error_log('No parent');
8659
                    }
8660
                    $organization->appendChild($my_item);
8661
                }
8662
8663
                // Get the path of the file(s) from the course directory root.
8664
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
8665
                $my_xml_file_path = $my_file_path;
8666
                if (!empty($path_to_remove)) {
8667
                    // From docs
8668
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
8669
8670
                    // From quiz
8671
                    if ('chamilo_scorm_export' === $this->ref) {
8672
                        $path_to_remove = 'scorm/'.$this->path.'/';
8673
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
8674
                    }
8675
                }
8676
8677
                $my_sub_dir = dirname($my_file_path);
8678
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
8679
                $my_xml_sub_dir = $my_sub_dir;
8680
                // Give a <resource> child to the <resources> element
8681
                $my_resource = $xmldoc->createElement('resource');
8682
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
8683
                $my_resource->setAttribute('type', 'webcontent');
8684
                $my_resource->setAttribute('href', $my_xml_file_path);
8685
                // adlcp:scormtype can be either 'sco' or 'asset'.
8686
                if ('sco' === $item->type) {
8687
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
8688
                } else {
8689
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
8690
                }
8691
                // xml:base is the base directory to find the files declared in this resource.
8692
                $my_resource->setAttribute('xml:base', '');
8693
                // Give a <file> child to the <resource> element.
8694
                $my_file = $xmldoc->createElement('file');
8695
                $my_file->setAttribute('href', $my_xml_file_path);
8696
                $my_resource->appendChild($my_file);
8697
8698
                // Dependency to other files - not yet supported.
8699
                $i = 1;
8700
                if ($inc_docs) {
8701
                    foreach ($inc_docs as $doc_info) {
8702
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
8703
                            continue;
8704
                        }
8705
                        $my_dep = $xmldoc->createElement('resource');
8706
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
8707
                        $my_dep->setAttribute('identifier', $res_id);
8708
                        $my_dep->setAttribute('type', 'webcontent');
8709
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
8710
                        $my_dep_file = $xmldoc->createElement('file');
8711
                        // Check type of URL.
8712
                        if ('remote' == $doc_info[1]) {
8713
                            // Remote file. Save url as is.
8714
                            $my_dep_file->setAttribute('href', $doc_info[0]);
8715
                            $my_dep->setAttribute('xml:base', '');
8716
                        } elseif ('local' === $doc_info[1]) {
8717
                            switch ($doc_info[2]) {
8718
                                case 'url':
8719
                                    // Local URL - save path as url for now, don't zip file.
8720
                                    $abs_path = api_get_path(SYS_PATH).
8721
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
8722
                                    $current_dir = dirname($abs_path);
8723
                                    $current_dir = str_replace('\\', '/', $current_dir);
8724
                                    $file_path = realpath($abs_path);
8725
                                    $file_path = str_replace('\\', '/', $file_path);
8726
                                    $my_dep_file->setAttribute('href', $file_path);
8727
                                    $my_dep->setAttribute('xml:base', '');
8728
                                    if (false !== strstr($file_path, $main_path)) {
8729
                                        // The calculated real path is really inside Chamilo's root path.
8730
                                        // Reduce file path to what's under the DocumentRoot.
8731
                                        $replace = $file_path;
8732
                                        $file_path = substr($file_path, strlen($root_path) - 1);
8733
                                        $destinationFile = $file_path;
8734
8735
                                        if (false !== strstr($file_path, 'upload/users')) {
8736
                                            $pos = strpos($file_path, 'my_files/');
8737
                                            if (false !== $pos) {
8738
                                                $onlyDirectory = str_replace(
8739
                                                    'upload/users/',
8740
                                                    '',
8741
                                                    substr($file_path, $pos, strlen($file_path))
8742
                                                );
8743
                                            }
8744
                                            $replace = $onlyDirectory;
8745
                                            $destinationFile = $replace;
8746
                                        }
8747
                                        $zip_files_abs[] = $file_path;
8748
                                        $link_updates[$my_file_path][] = [
8749
                                            'orig' => $doc_info[0],
8750
                                            'dest' => $destinationFile,
8751
                                            'replace' => $replace,
8752
                                        ];
8753
                                        $my_dep_file->setAttribute('href', $file_path);
8754
                                        $my_dep->setAttribute('xml:base', '');
8755
                                    } elseif (empty($file_path)) {
8756
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
8757
                                        $file_path = str_replace('//', '/', $file_path);
8758
                                        if (file_exists($file_path)) {
8759
                                            // We get the relative path.
8760
                                            $file_path = substr($file_path, strlen($current_dir));
8761
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
8762
                                            $link_updates[$my_file_path][] = [
8763
                                                'orig' => $doc_info[0],
8764
                                                'dest' => $file_path,
8765
                                            ];
8766
                                            $my_dep_file->setAttribute('href', $file_path);
8767
                                            $my_dep->setAttribute('xml:base', '');
8768
                                        }
8769
                                    }
8770
                                    break;
8771
                                case 'abs':
8772
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
8773
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
8774
                                    $my_dep->setAttribute('xml:base', '');
8775
8776
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
8777
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
8778
                                    $abs_img_path_without_subdir = $doc_info[0];
8779
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
8780
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
8781
                                    if (0 === $pos) {
8782
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
8783
                                    }
8784
8785
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
8786
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
8787
8788
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
8789
                                    $cur_path = '/' == substr($current_course_path, -1) ? $current_course_path : $current_course_path.'/';
8790
                                    // Check if the current document is in that path.
8791
                                    if (false !== strstr($file_path, $cur_path)) {
8792
                                        $destinationFile = substr($file_path, strlen($cur_path));
8793
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
8794
8795
                                        $fileToTest = $cur_path.$my_file_path;
8796
                                        if (!empty($path_to_remove)) {
8797
                                            $fileToTest = str_replace(
8798
                                                $path_to_remove.'/',
8799
                                                $path_to_replace,
8800
                                                $cur_path.$my_file_path
8801
                                            );
8802
                                        }
8803
8804
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
8805
8806
                                        // Put the current document in the zip (this array is the array
8807
                                        // that will manage documents already in the course folder - relative).
8808
                                        $zip_files[] = $filePathNoCoursePart;
8809
                                        // Update the links to the current document in the
8810
                                        // containing document (make them relative).
8811
                                        $link_updates[$my_file_path][] = [
8812
                                            'orig' => $doc_info[0],
8813
                                            'dest' => $destinationFile,
8814
                                            'replace' => $relative_path,
8815
                                        ];
8816
8817
                                        $my_dep_file->setAttribute('href', $file_path);
8818
                                        $my_dep->setAttribute('xml:base', '');
8819
                                    } elseif (false !== strstr($file_path, $main_path)) {
8820
                                        // The calculated real path is really inside Chamilo's root path.
8821
                                        // Reduce file path to what's under the DocumentRoot.
8822
                                        $file_path = substr($file_path, strlen($root_path));
8823
                                        $zip_files_abs[] = $file_path;
8824
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
8825
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
8826
                                        $my_dep->setAttribute('xml:base', '');
8827
                                    } elseif (empty($file_path)) {
8828
                                        // Probably this is an image inside "/main" directory
8829
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
8830
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
8831
8832
                                        if (file_exists($file_path)) {
8833
                                            if (false !== strstr($file_path, 'main/default_course_document')) {
8834
                                                // We get the relative path.
8835
                                                $pos = strpos($file_path, 'main/default_course_document/');
8836
                                                if (false !== $pos) {
8837
                                                    $onlyDirectory = str_replace(
8838
                                                        'main/default_course_document/',
8839
                                                        '',
8840
                                                        substr($file_path, $pos, strlen($file_path))
8841
                                                    );
8842
                                                }
8843
8844
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
8845
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
8846
                                                $zip_files_abs[] = $fileAbs;
8847
                                                $link_updates[$my_file_path][] = [
8848
                                                    'orig' => $doc_info[0],
8849
                                                    'dest' => $destinationFile,
8850
                                                ];
8851
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
8852
                                                $my_dep->setAttribute('xml:base', '');
8853
                                            }
8854
                                        }
8855
                                    }
8856
                                    break;
8857
                                case 'rel':
8858
                                    // Path relative to the current document.
8859
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
8860
                                    if ('..' === substr($doc_info[0], 0, 2)) {
8861
                                        // Relative path going up.
8862
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
8863
                                        $current_dir = str_replace('\\', '/', $current_dir);
8864
                                        $file_path = realpath($current_dir.$doc_info[0]);
8865
                                        $file_path = str_replace('\\', '/', $file_path);
8866
                                        if (false !== strstr($file_path, $main_path)) {
8867
                                            // The calculated real path is really inside Chamilo's root path.
8868
                                            // Reduce file path to what's under the DocumentRoot.
8869
                                            $file_path = substr($file_path, strlen($root_path));
8870
                                            $zip_files_abs[] = $file_path;
8871
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
8872
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
8873
                                            $my_dep->setAttribute('xml:base', '');
8874
                                        }
8875
                                    } else {
8876
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
8877
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
8878
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
8879
                                    }
8880
                                    break;
8881
                                default:
8882
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
8883
                                    $my_dep->setAttribute('xml:base', '');
8884
                                    break;
8885
                            }
8886
                        }
8887
                        $my_dep->appendChild($my_dep_file);
8888
                        $resources->appendChild($my_dep);
8889
                        $dependency = $xmldoc->createElement('dependency');
8890
                        $dependency->setAttribute('identifierref', $res_id);
8891
                        $my_resource->appendChild($dependency);
8892
                        $i++;
8893
                    }
8894
                }
8895
                $resources->appendChild($my_resource);
8896
                $zip_files[] = $my_file_path;
8897
            } else {
8898
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
8899
                switch ($item->type) {
8900
                    case TOOL_LINK:
8901
                        $my_item = $xmldoc->createElement('item');
8902
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
8903
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
8904
                        $my_item->setAttribute('isvisible', 'true');
8905
                        // Give a child element <title> to the <item> element.
8906
                        $my_title = $xmldoc->createElement(
8907
                            'title',
8908
                            htmlspecialchars(
8909
                                api_utf8_encode($item->get_title()),
8910
                                ENT_QUOTES,
8911
                                'UTF-8'
8912
                            )
8913
                        );
8914
                        $my_item->appendChild($my_title);
8915
                        // Give a child element <adlcp:prerequisites> to the <item> element.
8916
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
8917
                        $my_prereqs->setAttribute('type', 'aicc_script');
8918
                        $my_item->appendChild($my_prereqs);
8919
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
8920
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
8921
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
8922
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
8923
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
8924
                        //$xmldoc->createElement('adlcp:datafromlms', '');
8925
                        // Give a child element <adlcp:masteryscore> to the <item> element.
8926
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
8927
                        $my_item->appendChild($my_masteryscore);
8928
8929
                        // Attach this item to the organization element or its parent if there is one.
8930
                        if (!empty($item->parent) && 0 != $item->parent) {
8931
                            $children = $organization->childNodes;
8932
                            for ($i = 0; $i < $children->length; $i++) {
8933
                                $item_temp = $children->item($i);
8934
                                if ('item' == $item_temp->nodeName) {
8935
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
8936
                                        $item_temp->appendChild($my_item);
8937
                                    }
8938
                                }
8939
                            }
8940
                        } else {
8941
                            $organization->appendChild($my_item);
8942
                        }
8943
8944
                        $my_file_path = 'link_'.$item->get_id().'.html';
8945
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
8946
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
8947
                        $rs = Database::query($sql);
8948
                        if ($link = Database::fetch_array($rs)) {
8949
                            $url = $link['url'];
8950
                            $title = stripslashes($link['title']);
8951
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
8952
                            $my_xml_file_path = $my_file_path;
8953
                            $my_sub_dir = dirname($my_file_path);
8954
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
8955
                            $my_xml_sub_dir = $my_sub_dir;
8956
                            // Give a <resource> child to the <resources> element.
8957
                            $my_resource = $xmldoc->createElement('resource');
8958
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
8959
                            $my_resource->setAttribute('type', 'webcontent');
8960
                            $my_resource->setAttribute('href', $my_xml_file_path);
8961
                            // adlcp:scormtype can be either 'sco' or 'asset'.
8962
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
8963
                            // xml:base is the base directory to find the files declared in this resource.
8964
                            $my_resource->setAttribute('xml:base', '');
8965
                            // give a <file> child to the <resource> element.
8966
                            $my_file = $xmldoc->createElement('file');
8967
                            $my_file->setAttribute('href', $my_xml_file_path);
8968
                            $my_resource->appendChild($my_file);
8969
                            $resources->appendChild($my_resource);
8970
                        }
8971
                        break;
8972
                    case TOOL_QUIZ:
8973
                        $exe_id = $item->path;
8974
                        // Should be using ref when everything will be cleaned up in this regard.
8975
                        $exe = new Exercise();
8976
                        $exe->read($exe_id);
8977
                        $my_item = $xmldoc->createElement('item');
8978
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
8979
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
8980
                        $my_item->setAttribute('isvisible', 'true');
8981
                        // Give a child element <title> to the <item> element.
8982
                        $my_title = $xmldoc->createElement(
8983
                            'title',
8984
                            htmlspecialchars(
8985
                                api_utf8_encode($item->get_title()),
8986
                                ENT_QUOTES,
8987
                                'UTF-8'
8988
                            )
8989
                        );
8990
                        $my_item->appendChild($my_title);
8991
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
8992
                        $my_item->appendChild($my_max_score);
8993
                        // Give a child element <adlcp:prerequisites> to the <item> element.
8994
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
8995
                        $my_prereqs->setAttribute('type', 'aicc_script');
8996
                        $my_item->appendChild($my_prereqs);
8997
                        // Give a child element <adlcp:masteryscore> to the <item> element.
8998
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
8999
                        $my_item->appendChild($my_masteryscore);
9000
9001
                        // Attach this item to the organization element or hits parent if there is one.
9002
                        if (!empty($item->parent) && 0 != $item->parent) {
9003
                            $children = $organization->childNodes;
9004
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
9005
                            if ($possible_parent) {
9006
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
9007
                                    $possible_parent->appendChild($my_item);
9008
                                }
9009
                            }
9010
                        } else {
9011
                            $organization->appendChild($my_item);
9012
                        }
9013
9014
                        // Get the path of the file(s) from the course directory root
9015
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
9016
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
9017
                        // Write the contents of the exported exercise into a (big) html file
9018
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
9019
                        $scormExercise = new ScormExercise($exe, true);
9020
                        $contents = $scormExercise->export();
9021
9022
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
9023
                        $res = file_put_contents($tmp_file_path, $contents);
9024
                        if (false === $res) {
9025
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
9026
                        }
9027
                        $files_cleanup[] = $tmp_file_path;
9028
                        $my_xml_file_path = $my_file_path;
9029
                        $my_sub_dir = dirname($my_file_path);
9030
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
9031
                        $my_xml_sub_dir = $my_sub_dir;
9032
                        // Give a <resource> child to the <resources> element.
9033
                        $my_resource = $xmldoc->createElement('resource');
9034
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
9035
                        $my_resource->setAttribute('type', 'webcontent');
9036
                        $my_resource->setAttribute('href', $my_xml_file_path);
9037
                        // adlcp:scormtype can be either 'sco' or 'asset'.
9038
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
9039
                        // xml:base is the base directory to find the files declared in this resource.
9040
                        $my_resource->setAttribute('xml:base', '');
9041
                        // Give a <file> child to the <resource> element.
9042
                        $my_file = $xmldoc->createElement('file');
9043
                        $my_file->setAttribute('href', $my_xml_file_path);
9044
                        $my_resource->appendChild($my_file);
9045
9046
                        // Get included docs.
9047
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
9048
9049
                        // Dependency to other files - not yet supported.
9050
                        $i = 1;
9051
                        foreach ($inc_docs as $doc_info) {
9052
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
9053
                                continue;
9054
                            }
9055
                            $my_dep = $xmldoc->createElement('resource');
9056
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
9057
                            $my_dep->setAttribute('identifier', $res_id);
9058
                            $my_dep->setAttribute('type', 'webcontent');
9059
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
9060
                            $my_dep_file = $xmldoc->createElement('file');
9061
                            // Check type of URL.
9062
                            if ('remote' == $doc_info[1]) {
9063
                                // Remote file. Save url as is.
9064
                                $my_dep_file->setAttribute('href', $doc_info[0]);
9065
                                $my_dep->setAttribute('xml:base', '');
9066
                            } elseif ('local' == $doc_info[1]) {
9067
                                switch ($doc_info[2]) {
9068
                                    case 'url': // Local URL - save path as url for now, don't zip file.
9069
                                        // Save file but as local file (retrieve from URL).
9070
                                        $abs_path = api_get_path(SYS_PATH).
9071
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
9072
                                        $current_dir = dirname($abs_path);
9073
                                        $current_dir = str_replace('\\', '/', $current_dir);
9074
                                        $file_path = realpath($abs_path);
9075
                                        $file_path = str_replace('\\', '/', $file_path);
9076
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
9077
                                        $my_dep->setAttribute('xml:base', '');
9078
                                        if (false !== strstr($file_path, $main_path)) {
9079
                                            // The calculated real path is really inside the chamilo root path.
9080
                                            // Reduce file path to what's under the DocumentRoot.
9081
                                            $file_path = substr($file_path, strlen($root_path));
9082
                                            $zip_files_abs[] = $file_path;
9083
                                            $link_updates[$my_file_path][] = [
9084
                                                'orig' => $doc_info[0],
9085
                                                'dest' => 'document/'.$file_path,
9086
                                            ];
9087
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
9088
                                            $my_dep->setAttribute('xml:base', '');
9089
                                        } elseif (empty($file_path)) {
9090
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
9091
                                            $file_path = str_replace('//', '/', $file_path);
9092
                                            if (file_exists($file_path)) {
9093
                                                $file_path = substr($file_path, strlen($current_dir));
9094
                                                // We get the relative path.
9095
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
9096
                                                $link_updates[$my_file_path][] = [
9097
                                                    'orig' => $doc_info[0],
9098
                                                    'dest' => 'document/'.$file_path,
9099
                                                ];
9100
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9101
                                                $my_dep->setAttribute('xml:base', '');
9102
                                            }
9103
                                        }
9104
                                        break;
9105
                                    case 'abs':
9106
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
9107
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
9108
                                        $current_dir = str_replace('\\', '/', $current_dir);
9109
                                        $file_path = realpath($doc_info[0]);
9110
                                        $file_path = str_replace('\\', '/', $file_path);
9111
                                        $my_dep_file->setAttribute('href', $file_path);
9112
                                        $my_dep->setAttribute('xml:base', '');
9113
9114
                                        if (false !== strstr($file_path, $main_path)) {
9115
                                            // The calculated real path is really inside the chamilo root path.
9116
                                            // Reduce file path to what's under the DocumentRoot.
9117
                                            $file_path = substr($file_path, strlen($root_path));
9118
                                            $zip_files_abs[] = $file_path;
9119
                                            $link_updates[$my_file_path][] = [
9120
                                                'orig' => $doc_info[0],
9121
                                                'dest' => $file_path,
9122
                                            ];
9123
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
9124
                                            $my_dep->setAttribute('xml:base', '');
9125
                                        } elseif (empty($file_path)) {
9126
                                            $docSysPartPath = str_replace(
9127
                                                api_get_path(REL_COURSE_PATH),
9128
                                                '',
9129
                                                $doc_info[0]
9130
                                            );
9131
9132
                                            $docSysPartPathNoCourseCode = str_replace(
9133
                                                $_course['directory'].'/',
9134
                                                '',
9135
                                                $docSysPartPath
9136
                                            );
9137
9138
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
9139
                                            if (file_exists($docSysPath)) {
9140
                                                $file_path = $docSysPartPathNoCourseCode;
9141
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
9142
                                                $link_updates[$my_file_path][] = [
9143
                                                    'orig' => $doc_info[0],
9144
                                                    'dest' => $file_path,
9145
                                                ];
9146
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9147
                                                $my_dep->setAttribute('xml:base', '');
9148
                                            }
9149
                                        }
9150
                                        break;
9151
                                    case 'rel':
9152
                                        // Path relative to the current document. Save xml:base as current document's
9153
                                        // directory and save file in zip as subdir.file_path
9154
                                        if ('..' === substr($doc_info[0], 0, 2)) {
9155
                                            // Relative path going up.
9156
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
9157
                                            $current_dir = str_replace('\\', '/', $current_dir);
9158
                                            $file_path = realpath($current_dir.$doc_info[0]);
9159
                                            $file_path = str_replace('\\', '/', $file_path);
9160
                                            if (false !== strstr($file_path, $main_path)) {
9161
                                                // The calculated real path is really inside Chamilo's root path.
9162
                                                // Reduce file path to what's under the DocumentRoot.
9163
9164
                                                $file_path = substr($file_path, strlen($root_path));
9165
                                                $file_path_dest = $file_path;
9166
9167
                                                // File path is courses/CHAMILO/document/....
9168
                                                $info_file_path = explode('/', $file_path);
9169
                                                if ('courses' == $info_file_path[0]) {
9170
                                                    // Add character "/" in file path.
9171
                                                    $file_path_dest = 'document/'.$file_path;
9172
                                                }
9173
                                                $zip_files_abs[] = $file_path;
9174
9175
                                                $link_updates[$my_file_path][] = [
9176
                                                    'orig' => $doc_info[0],
9177
                                                    'dest' => $file_path_dest,
9178
                                                ];
9179
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9180
                                                $my_dep->setAttribute('xml:base', '');
9181
                                            }
9182
                                        } else {
9183
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
9184
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
9185
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
9186
                                        }
9187
                                        break;
9188
                                    default:
9189
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
9190
                                        $my_dep->setAttribute('xml:base', '');
9191
                                        break;
9192
                                }
9193
                            }
9194
                            $my_dep->appendChild($my_dep_file);
9195
                            $resources->appendChild($my_dep);
9196
                            $dependency = $xmldoc->createElement('dependency');
9197
                            $dependency->setAttribute('identifierref', $res_id);
9198
                            $my_resource->appendChild($dependency);
9199
                            $i++;
9200
                        }
9201
                        $resources->appendChild($my_resource);
9202
                        $zip_files[] = $my_file_path;
9203
                        break;
9204
                    default:
9205
                        // Get the path of the file(s) from the course directory root
9206
                        $my_file_path = 'non_exportable.html';
9207
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
9208
                        $my_xml_file_path = $my_file_path;
9209
                        $my_sub_dir = dirname($my_file_path);
9210
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
9211
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
9212
                        $my_xml_sub_dir = $my_sub_dir;
9213
                        // Give a <resource> child to the <resources> element.
9214
                        $my_resource = $xmldoc->createElement('resource');
9215
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
9216
                        $my_resource->setAttribute('type', 'webcontent');
9217
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
9218
                        // adlcp:scormtype can be either 'sco' or 'asset'.
9219
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
9220
                        // xml:base is the base directory to find the files declared in this resource.
9221
                        $my_resource->setAttribute('xml:base', '');
9222
                        // Give a <file> child to the <resource> element.
9223
                        $my_file = $xmldoc->createElement('file');
9224
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
9225
                        $my_resource->appendChild($my_file);
9226
                        $resources->appendChild($my_resource);
9227
                        break;
9228
                }
9229
            }
9230
        }
9231
        $organizations->appendChild($organization);
9232
        $root->appendChild($organizations);
9233
        $root->appendChild($resources);
9234
        $xmldoc->appendChild($root);
9235
9236
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
9237
9238
        // then add the file to the zip, then destroy the file (this is done automatically).
9239
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
9240
        foreach ($zip_files as $file_path) {
9241
            if (empty($file_path)) {
9242
                continue;
9243
            }
9244
9245
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
9246
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
9247
9248
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
9249
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
9250
            }
9251
9252
            $this->create_path($dest_file);
9253
            @copy($filePath, $dest_file);
9254
9255
            // Check if the file needs a link update.
9256
            if (in_array($file_path, array_keys($link_updates))) {
9257
                $string = file_get_contents($dest_file);
9258
                unlink($dest_file);
9259
                foreach ($link_updates[$file_path] as $old_new) {
9260
                    // This is an ugly hack that allows .flv files to be found by the flv player that
9261
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
9262
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
9263
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
9264
                    if ('flv' === substr($old_new['dest'], -3) &&
9265
                        'main/' === substr($old_new['dest'], 0, 5)
9266
                    ) {
9267
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
9268
                    } elseif ('flv' === substr($old_new['dest'], -3) &&
9269
                        'video/' === substr($old_new['dest'], 0, 6)
9270
                    ) {
9271
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
9272
                    }
9273
9274
                    // Fix to avoid problems with default_course_document
9275
                    if (false === strpos('main/default_course_document', $old_new['dest'])) {
9276
                        $newDestination = $old_new['dest'];
9277
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
9278
                            $newDestination = $old_new['replace'];
9279
                        }
9280
                    } else {
9281
                        $newDestination = str_replace('document/', '', $old_new['dest']);
9282
                    }
9283
                    $string = str_replace($old_new['orig'], $newDestination, $string);
9284
9285
                    // Add files inside the HTMLs
9286
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
9287
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
9288
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
9289
                        copy(
9290
                            $sys_course_path.$new_path,
9291
                            $destinationFile
9292
                        );
9293
                    }
9294
                }
9295
                file_put_contents($dest_file, $string);
9296
            }
9297
9298
            if (file_exists($filePath) && $copyAll) {
9299
                $extension = $this->get_extension($filePath);
9300
                if (in_array($extension, ['html', 'html'])) {
9301
                    $containerOrigin = dirname($filePath);
9302
                    $containerDestination = dirname($dest_file);
9303
9304
                    $finder = new Finder();
9305
                    $finder->files()->in($containerOrigin)
9306
                        ->notName('*_DELETED_*')
9307
                        ->exclude('share_folder')
9308
                        ->exclude('chat_files')
9309
                        ->exclude('certificates')
9310
                    ;
9311
9312
                    if (is_dir($containerOrigin) &&
9313
                        is_dir($containerDestination)
9314
                    ) {
9315
                        $fs = new Filesystem();
9316
                        $fs->mirror(
9317
                            $containerOrigin,
9318
                            $containerDestination,
9319
                            $finder
9320
                        );
9321
                    }
9322
                }
9323
            }
9324
        }
9325
9326
        foreach ($zip_files_abs as $file_path) {
9327
            if (empty($file_path)) {
9328
                continue;
9329
            }
9330
9331
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
9332
                continue;
9333
            }
9334
9335
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
9336
            if (false !== strstr($file_path, 'upload/users')) {
9337
                $pos = strpos($file_path, 'my_files/');
9338
                if (false !== $pos) {
9339
                    $onlyDirectory = str_replace(
9340
                        'upload/users/',
9341
                        '',
9342
                        substr($file_path, $pos, strlen($file_path))
9343
                    );
9344
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
9345
                }
9346
            }
9347
9348
            if (false !== strstr($file_path, 'default_course_document/')) {
9349
                $replace = str_replace('/main', '', $file_path);
9350
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
9351
            }
9352
9353
            if (empty($dest_file)) {
9354
                continue;
9355
            }
9356
9357
            $this->create_path($dest_file);
9358
            copy($main_path.$file_path, $dest_file);
9359
            // Check if the file needs a link update.
9360
            if (in_array($file_path, array_keys($link_updates))) {
9361
                $string = file_get_contents($dest_file);
9362
                unlink($dest_file);
9363
                foreach ($link_updates[$file_path] as $old_new) {
9364
                    // This is an ugly hack that allows .flv files to be found by the flv player that
9365
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
9366
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
9367
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
9368
                    if ('flv' == substr($old_new['dest'], -3) &&
9369
                        'main/' == substr($old_new['dest'], 0, 5)
9370
                    ) {
9371
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
9372
                    }
9373
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
9374
                }
9375
                file_put_contents($dest_file, $string);
9376
            }
9377
        }
9378
9379
        if (is_array($links_to_create)) {
9380
            foreach ($links_to_create as $file => $link) {
9381
                $content = '<!DOCTYPE html><head>
9382
                            <meta charset="'.api_get_language_isocode().'" />
9383
                            <title>'.$link['title'].'</title>
9384
                            </head>
9385
                            <body dir="'.api_get_text_direction().'">
9386
                            <div style="text-align:center">
9387
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
9388
                            </body>
9389
                            </html>';
9390
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
9391
            }
9392
        }
9393
9394
        // Add non exportable message explanation.
9395
        $lang_not_exportable = get_lang('This learning object or activity is not SCORM compliant. That\'s why it is not exportable.');
9396
        $file_content = '<!DOCTYPE html><head>
9397
                        <meta charset="'.api_get_language_isocode().'" />
9398
                        <title>'.$lang_not_exportable.'</title>
9399
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
9400
                        </head>
9401
                        <body dir="'.api_get_text_direction().'">';
9402
        $file_content .=
9403
            <<<EOD
9404
                    <style>
9405
            .error-message {
9406
                font-family: arial, verdana, helvetica, sans-serif;
9407
                border-width: 1px;
9408
                border-style: solid;
9409
                left: 50%;
9410
                margin: 10px auto;
9411
                min-height: 30px;
9412
                padding: 5px;
9413
                right: 50%;
9414
                width: 500px;
9415
                background-color: #FFD1D1;
9416
                border-color: #FF0000;
9417
                color: #000;
9418
            }
9419
        </style>
9420
    <body>
9421
        <div class="error-message">
9422
            $lang_not_exportable
9423
        </div>
9424
    </body>
9425
</html>
9426
EOD;
9427
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
9428
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
9429
        }
9430
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
9431
9432
        // Add the extra files that go along with a SCORM package.
9433
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
9434
9435
        $fs = new Filesystem();
9436
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
9437
9438
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
9439
        $manifest = @$xmldoc->saveXML();
9440
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
9441
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
9442
        $zip_folder->add(
9443
            $archivePath.'/'.$temp_dir_short,
9444
            PCLZIP_OPT_REMOVE_PATH,
9445
            $archivePath.'/'.$temp_dir_short.'/'
9446
        );
9447
9448
        // Clean possible temporary files.
9449
        foreach ($files_cleanup as $file) {
9450
            $res = unlink($file);
9451
            if (false === $res) {
9452
                error_log(
9453
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
9454
                    0
9455
                );
9456
            }
9457
        }
9458
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
9459
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
9460
    }
9461
9462
    /**
9463
     * @param int $lp_id
9464
     *
9465
     * @return bool
9466
     */
9467
    public function scorm_export_to_pdf($lp_id)
9468
    {
9469
        $lp_id = (int) $lp_id;
9470
        $files_to_export = [];
9471
9472
        $sessionId = api_get_session_id();
9473
        $course_data = api_get_course_info($this->cc);
9474
9475
        if (!empty($course_data)) {
9476
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
9477
            $list = self::get_flat_ordered_items_list($lp_id);
9478
            if (!empty($list)) {
9479
                foreach ($list as $item_id) {
9480
                    $item = $this->items[$item_id];
9481
                    switch ($item->type) {
9482
                        case 'document':
9483
                            // Getting documents from a LP with chamilo documents
9484
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
9485
                            // Try loading document from the base course.
9486
                            if (empty($file_data) && !empty($sessionId)) {
9487
                                $file_data = DocumentManager::get_document_data_by_id(
9488
                                    $item->path,
9489
                                    $this->cc,
9490
                                    false,
9491
                                    0
9492
                                );
9493
                            }
9494
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
9495
                            if (file_exists($file_path)) {
9496
                                $files_to_export[] = [
9497
                                    'title' => $item->get_title(),
9498
                                    'path' => $file_path,
9499
                                ];
9500
                            }
9501
                            break;
9502
                        case 'asset': //commes from a scorm package generated by chamilo
9503
                        case 'sco':
9504
                            $file_path = $scorm_path.'/'.$item->path;
9505
                            if (file_exists($file_path)) {
9506
                                $files_to_export[] = [
9507
                                    'title' => $item->get_title(),
9508
                                    'path' => $file_path,
9509
                                ];
9510
                            }
9511
                            break;
9512
                        case 'dir':
9513
                            $files_to_export[] = [
9514
                                'title' => $item->get_title(),
9515
                                'path' => null,
9516
                            ];
9517
                            break;
9518
                    }
9519
                }
9520
            }
9521
9522
            $pdf = new PDF();
9523
            $result = $pdf->html_to_pdf(
9524
                $files_to_export,
9525
                $this->name,
9526
                $this->cc,
9527
                true,
9528
                true,
9529
                true,
9530
                $this->get_name()
9531
            );
9532
9533
            return $result;
9534
        }
9535
9536
        return false;
9537
    }
9538
9539
    /**
9540
     * Temp function to be moved in main_api or the best place around for this.
9541
     * Creates a file path if it doesn't exist.
9542
     *
9543
     * @param string $path
9544
     */
9545
    public function create_path($path)
9546
    {
9547
        $path_bits = explode('/', dirname($path));
9548
9549
        // IS_WINDOWS_OS has been defined in main_api.lib.php
9550
        $path_built = IS_WINDOWS_OS ? '' : '/';
9551
        foreach ($path_bits as $bit) {
9552
            if (!empty($bit)) {
9553
                $new_path = $path_built.$bit;
9554
                if (is_dir($new_path)) {
9555
                    $path_built = $new_path.'/';
9556
                } else {
9557
                    mkdir($new_path, api_get_permissions_for_new_directories());
9558
                    $path_built = $new_path.'/';
9559
                }
9560
            }
9561
        }
9562
    }
9563
9564
    /**
9565
     * @param int    $lp_id
9566
     * @param string $status
9567
     */
9568
    public function set_autolaunch($lp_id, $status)
9569
    {
9570
        $course_id = api_get_course_int_id();
9571
        $lp_id = (int) $lp_id;
9572
        $status = (int) $status;
9573
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
9574
9575
        // Setting everything to autolaunch = 0
9576
        $attributes['autolaunch'] = 0;
9577
        $where = [
9578
            'session_id = ? AND c_id = ? ' => [
9579
                api_get_session_id(),
9580
                $course_id,
9581
            ],
9582
        ];
9583
        Database::update($lp_table, $attributes, $where);
9584
        if (1 == $status) {
9585
            //Setting my lp_id to autolaunch = 1
9586
            $attributes['autolaunch'] = 1;
9587
            $where = [
9588
                'iid = ? AND session_id = ? AND c_id = ?' => [
9589
                    $lp_id,
9590
                    api_get_session_id(),
9591
                    $course_id,
9592
                ],
9593
            ];
9594
            Database::update($lp_table, $attributes, $where);
9595
        }
9596
    }
9597
9598
    /**
9599
     * Gets previous_item_id for the next element of the lp_item table.
9600
     *
9601
     * @author Isaac flores paz
9602
     *
9603
     * @return int Previous item ID
9604
     */
9605
    public function select_previous_item_id()
9606
    {
9607
        $course_id = api_get_course_int_id();
9608
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9609
9610
        // Get the max order of the items
9611
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
9612
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9613
        $rs_max_order = Database::query($sql);
9614
        $row_max_order = Database::fetch_object($rs_max_order);
9615
        $max_order = $row_max_order->display_order;
9616
        // Get the previous item ID
9617
        $sql = "SELECT iid as previous FROM $table_lp_item
9618
                WHERE
9619
                    c_id = $course_id AND
9620
                    lp_id = ".$this->lp_id." AND
9621
                    display_order = '$max_order' ";
9622
        $rs_max = Database::query($sql);
9623
        $row_max = Database::fetch_object($rs_max);
9624
9625
        // Return the previous item ID
9626
        return $row_max->previous;
9627
    }
9628
9629
    /**
9630
     * Copies an LP.
9631
     */
9632
    public function copy()
9633
    {
9634
        // Course builder
9635
        $cb = new CourseBuilder();
9636
9637
        //Setting tools that will be copied
9638
        $cb->set_tools_to_build(['learnpaths']);
9639
9640
        //Setting elements that will be copied
9641
        $cb->set_tools_specific_id_list(
9642
            ['learnpaths' => [$this->lp_id]]
9643
        );
9644
9645
        $course = $cb->build();
9646
9647
        //Course restorer
9648
        $course_restorer = new CourseRestorer($course);
9649
        $course_restorer->set_add_text_in_items(true);
9650
        $course_restorer->set_tool_copy_settings(
9651
            ['learnpaths' => ['reset_dates' => true]]
9652
        );
9653
        $course_restorer->restore(
9654
            api_get_course_id(),
9655
            api_get_session_id(),
9656
            false,
9657
            false
9658
        );
9659
    }
9660
9661
    /**
9662
     * Verify document size.
9663
     *
9664
     * @param string $s
9665
     *
9666
     * @return bool
9667
     */
9668
    public static function verify_document_size($s)
9669
    {
9670
        $post_max = ini_get('post_max_size');
9671
        if ('M' == substr($post_max, -1, 1)) {
9672
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
9673
        } elseif ('G' == substr($post_max, -1, 1)) {
9674
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
9675
        }
9676
        $upl_max = ini_get('upload_max_filesize');
9677
        if ('M' == substr($upl_max, -1, 1)) {
9678
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
9679
        } elseif ('G' == substr($upl_max, -1, 1)) {
9680
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
9681
        }
9682
9683
        $repo = Container::getDocumentRepository();
9684
        $documents_total_space = $repo->getTotalSpace(api_get_course_int_id());
9685
9686
        $course_max_space = DocumentManager::get_course_quota();
9687
        $total_size = filesize($s) + $documents_total_space;
9688
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
9689
            return true;
9690
        }
9691
9692
        return false;
9693
    }
9694
9695
    /**
9696
     * Clear LP prerequisites.
9697
     */
9698
    public function clear_prerequisites()
9699
    {
9700
        $course_id = $this->get_course_int_id();
9701
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9702
        $lp_id = $this->get_id();
9703
        // Cleaning prerequisites
9704
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
9705
                WHERE c_id = $course_id AND lp_id = $lp_id";
9706
        Database::query($sql);
9707
9708
        // Cleaning mastery score for exercises
9709
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
9710
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
9711
        Database::query($sql);
9712
    }
9713
9714
    public function set_previous_step_as_prerequisite_for_all_items()
9715
    {
9716
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9717
        $course_id = $this->get_course_int_id();
9718
        $lp_id = $this->get_id();
9719
9720
        if (!empty($this->items)) {
9721
            $previous_item_id = null;
9722
            $previous_item_max = 0;
9723
            $previous_item_type = null;
9724
            $last_item_not_dir = null;
9725
            $last_item_not_dir_type = null;
9726
            $last_item_not_dir_max = null;
9727
9728
            foreach ($this->ordered_items as $itemId) {
9729
                $item = $this->getItem($itemId);
9730
                // if there was a previous item... (otherwise jump to set it)
9731
                if (!empty($previous_item_id)) {
9732
                    $current_item_id = $item->get_id(); //save current id
9733
                    if ('dir' != $item->get_type()) {
9734
                        // Current item is not a folder, so it qualifies to get a prerequisites
9735
                        if ('quiz' == $last_item_not_dir_type) {
9736
                            // if previous is quiz, mark its max score as default score to be achieved
9737
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
9738
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
9739
                            Database::query($sql);
9740
                        }
9741
                        // now simply update the prerequisite to set it to the last non-chapter item
9742
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
9743
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
9744
                        Database::query($sql);
9745
                        // record item as 'non-chapter' reference
9746
                        $last_item_not_dir = $item->get_id();
9747
                        $last_item_not_dir_type = $item->get_type();
9748
                        $last_item_not_dir_max = $item->get_max();
9749
                    }
9750
                } else {
9751
                    if ('dir' != $item->get_type()) {
9752
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
9753
                        $last_item_not_dir = $item->get_id();
9754
                        $last_item_not_dir_type = $item->get_type();
9755
                        $last_item_not_dir_max = $item->get_max();
9756
                    }
9757
                }
9758
                // Saving the item as "previous item" for the next loop
9759
                $previous_item_id = $item->get_id();
9760
                $previous_item_max = $item->get_max();
9761
                $previous_item_type = $item->get_type();
9762
            }
9763
        }
9764
    }
9765
9766
    /**
9767
     * @param array $params
9768
     *
9769
     * @return int
9770
     */
9771
    public static function createCategory($params)
9772
    {
9773
        $courseEntity = api_get_course_entity(api_get_course_int_id());
9774
9775
        $item = new CLpCategory();
9776
        $item
9777
            ->setName($params['name'])
9778
            ->setCId($params['c_id'])
9779
            ->setParent($courseEntity)
9780
            ->addCourseLink($courseEntity, api_get_session_entity())
9781
        ;
9782
9783
        $repo = Container::getLpCategoryRepository();
9784
        $repo->create($item);
9785
9786
        /*api_item_property_update(
9787
            api_get_course_info(),
9788
            TOOL_LEARNPATH_CATEGORY,
9789
            $item->getId(),
9790
            'visible',
9791
            api_get_user_id()
9792
        );*/
9793
9794
        return $item->getIid();
9795
    }
9796
9797
    /**
9798
     * @param array $params
9799
     */
9800
    public static function updateCategory($params)
9801
    {
9802
        $em = Database::getManager();
9803
        /** @var CLpCategory $item */
9804
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
9805
        if ($item) {
9806
            $item->setName($params['name']);
9807
            $em->persist($item);
9808
            $em->flush();
9809
        }
9810
    }
9811
9812
    /**
9813
     * @param int $id
9814
     */
9815
    public static function moveUpCategory($id)
9816
    {
9817
        $id = (int) $id;
9818
        $em = Database::getManager();
9819
        /** @var CLpCategory $item */
9820
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
9821
        if ($item) {
9822
            $position = $item->getPosition() - 1;
9823
            $item->setPosition($position);
9824
            $em->persist($item);
9825
            $em->flush();
9826
        }
9827
    }
9828
9829
    /**
9830
     * @param int $id
9831
     */
9832
    public static function moveDownCategory($id)
9833
    {
9834
        $id = (int) $id;
9835
        $em = Database::getManager();
9836
        /** @var CLpCategory $item */
9837
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
9838
        if ($item) {
9839
            $position = $item->getPosition() + 1;
9840
            $item->setPosition($position);
9841
            $em->persist($item);
9842
            $em->flush();
9843
        }
9844
    }
9845
9846
    public static function getLpList($courseId)
9847
    {
9848
        $table = Database::get_course_table(TABLE_LP_MAIN);
9849
        $courseId = (int) $courseId;
9850
9851
        $sql = "SELECT * FROM $table WHERE c_id = $courseId";
9852
        $result = Database::query($sql);
9853
9854
        return Database::store_result($result, 'ASSOC');
9855
    }
9856
9857
    /**
9858
     * @param int $courseId
9859
     *
9860
     * @throws \Doctrine\ORM\Query\QueryException
9861
     *
9862
     * @return int|mixed
9863
     */
9864
    public static function getCountCategories($courseId)
9865
    {
9866
        if (empty($courseId)) {
9867
            return 0;
9868
        }
9869
        $em = Database::getManager();
9870
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
9871
        $query->setParameter('id', $courseId);
9872
9873
        return $query->getSingleScalarResult();
9874
    }
9875
9876
    /**
9877
     * @param int $courseId
9878
     *
9879
     * @return CLpCategory[]
9880
     */
9881
    public static function getCategories($courseId)
9882
    {
9883
        $em = Database::getManager();
9884
9885
        // Using doctrine extensions
9886
        /** @var SortableRepository $repo */
9887
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
9888
9889
        return $repo->getBySortableGroupsQuery(['cId' => $courseId])->getResult();
9890
    }
9891
9892
    public static function getCategorySessionId($id)
9893
    {
9894
        if (false === api_get_configuration_value('allow_session_lp_category')) {
9895
            return 0;
9896
        }
9897
9898
        $table = Database::get_course_table(TABLE_LP_CATEGORY);
9899
        $id = (int) $id;
9900
9901
        $sql = "SELECT session_id FROM $table WHERE iid = $id";
9902
        $result = Database::query($sql);
9903
        $result = Database::fetch_array($result, 'ASSOC');
9904
9905
        if ($result) {
9906
            return (int) $result['session_id'];
9907
        }
9908
9909
        return 0;
9910
    }
9911
9912
    /**
9913
     * @param int $id
9914
     *
9915
     * @return CLpCategory
9916
     */
9917
    public static function getCategory($id)
9918
    {
9919
        $id = (int) $id;
9920
        $em = Database::getManager();
9921
9922
        return $em->find('ChamiloCourseBundle:CLpCategory', $id);
9923
    }
9924
9925
    /**
9926
     * @param int $courseId
9927
     *
9928
     * @return array
9929
     */
9930
    public static function getCategoryByCourse($courseId)
9931
    {
9932
        $em = Database::getManager();
9933
        $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
9934
            ['cId' => $courseId]
9935
        );
9936
9937
        return $items;
9938
    }
9939
9940
    /**
9941
     * @param int $id
9942
     */
9943
    public static function deleteCategory($id): bool
9944
    {
9945
        $repo = Container::getLpCategoryRepository();
9946
        /** @var CLpCategory $category */
9947
        $category = $repo->find($id);
9948
        if ($category) {
9949
            $em = Database::getManager();
9950
            $lps = $category->getLps();
9951
9952
            foreach ($lps as $lp) {
9953
                $lp->setCategory(null);
9954
                $em->persist($lp);
9955
            }
9956
9957
            // Removing category.
9958
            $em->remove($category);
9959
            $em->flush();
9960
9961
            return true;
9962
        }
9963
9964
        return false;
9965
    }
9966
9967
    /**
9968
     * @param int  $courseId
9969
     * @param bool $addSelectOption
9970
     *
9971
     * @return mixed
9972
     */
9973
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
9974
    {
9975
        $items = self::getCategoryByCourse($courseId);
9976
        $cats = [];
9977
        if ($addSelectOption) {
9978
            $cats = [get_lang('Select a category')];
9979
        }
9980
9981
        if (!empty($items)) {
9982
            foreach ($items as $cat) {
9983
                $cats[$cat->getIid()] = $cat->getName();
9984
            }
9985
        }
9986
9987
        return $cats;
9988
    }
9989
9990
    /**
9991
     * @param string $courseCode
9992
     * @param int    $lpId
9993
     * @param int    $user_id
9994
     *
9995
     * @return learnpath
9996
     */
9997
    public static function getLpFromSession($courseCode, $lpId, $user_id)
9998
    {
9999
        $debug = 0;
10000
        $learnPath = null;
10001
        $lpObject = Session::read('lpobject');
10002
        if (null !== $lpObject) {
10003
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
10004
            if ($debug) {
10005
                error_log('getLpFromSession: unserialize');
10006
                error_log('------getLpFromSession------');
10007
                error_log('------unserialize------');
10008
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
10009
                error_log("api_get_sessionid: ".api_get_session_id());
10010
            }
10011
        }
10012
10013
        if (!is_object($learnPath)) {
10014
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
10015
            if ($debug) {
10016
                error_log('------getLpFromSession------');
10017
                error_log('getLpFromSession: create new learnpath');
10018
                error_log("create new LP with $courseCode - $lpId - $user_id");
10019
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
10020
                error_log("api_get_sessionid: ".api_get_session_id());
10021
            }
10022
        }
10023
10024
        return $learnPath;
10025
    }
10026
10027
    /**
10028
     * @param int $itemId
10029
     *
10030
     * @return learnpathItem|false
10031
     */
10032
    public function getItem($itemId)
10033
    {
10034
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
10035
            return $this->items[$itemId];
10036
        }
10037
10038
        return false;
10039
    }
10040
10041
    /**
10042
     * @return int
10043
     */
10044
    public function getCurrentAttempt()
10045
    {
10046
        $attempt = $this->getItem($this->get_current_item_id());
10047
        if ($attempt) {
10048
            return $attempt->get_attempt_id();
10049
        }
10050
10051
        return 0;
10052
    }
10053
10054
    /**
10055
     * @return int
10056
     */
10057
    public function getCategoryId()
10058
    {
10059
        return (int) $this->categoryId;
10060
    }
10061
10062
    /**
10063
     * Get whether this is a learning path with the possibility to subscribe
10064
     * users or not.
10065
     *
10066
     * @return int
10067
     */
10068
    public function getSubscribeUsers()
10069
    {
10070
        return $this->subscribeUsers;
10071
    }
10072
10073
    /**
10074
     * Calculate the count of stars for a user in this LP
10075
     * This calculation is based on the following rules:
10076
     * - the student gets one star when he gets to 50% of the learning path
10077
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
10078
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
10079
     * - the student gets the final star when the score for the *last* test is >= 80%.
10080
     *
10081
     * @param int $sessionId Optional. The session ID
10082
     *
10083
     * @return int The count of stars
10084
     */
10085
    public function getCalculateStars($sessionId = 0)
10086
    {
10087
        $stars = 0;
10088
        $progress = self::getProgress(
10089
            $this->lp_id,
10090
            $this->user_id,
10091
            $this->course_int_id,
10092
            $sessionId
10093
        );
10094
10095
        if ($progress >= 50) {
10096
            $stars++;
10097
        }
10098
10099
        // Calculate stars chapters evaluation
10100
        $exercisesItems = $this->getExercisesItems();
10101
10102
        if (!empty($exercisesItems)) {
10103
            $totalResult = 0;
10104
10105
            foreach ($exercisesItems as $exerciseItem) {
10106
                $exerciseResultInfo = Event::getExerciseResultsByUser(
10107
                    $this->user_id,
10108
                    $exerciseItem->path,
10109
                    $this->course_int_id,
10110
                    $sessionId,
10111
                    $this->lp_id,
10112
                    $exerciseItem->db_id
10113
                );
10114
10115
                $exerciseResultInfo = end($exerciseResultInfo);
10116
10117
                if (!$exerciseResultInfo) {
10118
                    continue;
10119
                }
10120
10121
                if (!empty($exerciseResultInfo['max_score'])) {
10122
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
10123
                } else {
10124
                    $exerciseResult = 0;
10125
                }
10126
                $totalResult += $exerciseResult;
10127
            }
10128
10129
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
10130
10131
            if ($totalExerciseAverage >= 50) {
10132
                $stars++;
10133
            }
10134
10135
            if ($totalExerciseAverage >= 80) {
10136
                $stars++;
10137
            }
10138
        }
10139
10140
        // Calculate star for final evaluation
10141
        $finalEvaluationItem = $this->getFinalEvaluationItem();
10142
10143
        if (!empty($finalEvaluationItem)) {
10144
            $evaluationResultInfo = Event::getExerciseResultsByUser(
10145
                $this->user_id,
10146
                $finalEvaluationItem->path,
10147
                $this->course_int_id,
10148
                $sessionId,
10149
                $this->lp_id,
10150
                $finalEvaluationItem->db_id
10151
            );
10152
10153
            $evaluationResultInfo = end($evaluationResultInfo);
10154
10155
            if ($evaluationResultInfo) {
10156
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
10157
10158
                if ($evaluationResult >= 80) {
10159
                    $stars++;
10160
                }
10161
            }
10162
        }
10163
10164
        return $stars;
10165
    }
10166
10167
    /**
10168
     * Get the items of exercise type.
10169
     *
10170
     * @return array The items. Otherwise return false
10171
     */
10172
    public function getExercisesItems()
10173
    {
10174
        $exercises = [];
10175
        foreach ($this->items as $item) {
10176
            if ('quiz' != $item->type) {
10177
                continue;
10178
            }
10179
            $exercises[] = $item;
10180
        }
10181
10182
        array_pop($exercises);
10183
10184
        return $exercises;
10185
    }
10186
10187
    /**
10188
     * Get the item of exercise type (evaluation type).
10189
     *
10190
     * @return array The final evaluation. Otherwise return false
10191
     */
10192
    public function getFinalEvaluationItem()
10193
    {
10194
        $exercises = [];
10195
        foreach ($this->items as $item) {
10196
            if (TOOL_QUIZ !== $item->type) {
10197
                continue;
10198
            }
10199
10200
            $exercises[] = $item;
10201
        }
10202
10203
        return array_pop($exercises);
10204
    }
10205
10206
    /**
10207
     * Calculate the total points achieved for the current user in this learning path.
10208
     *
10209
     * @param int $sessionId Optional. The session Id
10210
     *
10211
     * @return int
10212
     */
10213
    public function getCalculateScore($sessionId = 0)
10214
    {
10215
        // Calculate stars chapters evaluation
10216
        $exercisesItems = $this->getExercisesItems();
10217
        $finalEvaluationItem = $this->getFinalEvaluationItem();
10218
        $totalExercisesResult = 0;
10219
        $totalEvaluationResult = 0;
10220
10221
        if (false !== $exercisesItems) {
10222
            foreach ($exercisesItems as $exerciseItem) {
10223
                $exerciseResultInfo = Event::getExerciseResultsByUser(
10224
                    $this->user_id,
10225
                    $exerciseItem->path,
10226
                    $this->course_int_id,
10227
                    $sessionId,
10228
                    $this->lp_id,
10229
                    $exerciseItem->db_id
10230
                );
10231
10232
                $exerciseResultInfo = end($exerciseResultInfo);
10233
10234
                if (!$exerciseResultInfo) {
10235
                    continue;
10236
                }
10237
10238
                $totalExercisesResult += $exerciseResultInfo['score'];
10239
            }
10240
        }
10241
10242
        if (!empty($finalEvaluationItem)) {
10243
            $evaluationResultInfo = Event::getExerciseResultsByUser(
10244
                $this->user_id,
10245
                $finalEvaluationItem->path,
10246
                $this->course_int_id,
10247
                $sessionId,
10248
                $this->lp_id,
10249
                $finalEvaluationItem->db_id
10250
            );
10251
10252
            $evaluationResultInfo = end($evaluationResultInfo);
10253
10254
            if ($evaluationResultInfo) {
10255
                $totalEvaluationResult += $evaluationResultInfo['score'];
10256
            }
10257
        }
10258
10259
        return $totalExercisesResult + $totalEvaluationResult;
10260
    }
10261
10262
    /**
10263
     * Check if URL is not allowed to be show in a iframe.
10264
     *
10265
     * @param string $src
10266
     *
10267
     * @return string
10268
     */
10269
    public function fixBlockedLinks($src)
10270
    {
10271
        $urlInfo = parse_url($src);
10272
10273
        $platformProtocol = 'https';
10274
        if (false === strpos(api_get_path(WEB_CODE_PATH), 'https')) {
10275
            $platformProtocol = 'http';
10276
        }
10277
10278
        $protocolFixApplied = false;
10279
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
10280
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
10281
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
10282
10283
        if ($platformProtocol != $scheme) {
10284
            Session::write('x_frame_source', $src);
10285
            $src = 'blank.php?error=x_frames_options';
10286
            $protocolFixApplied = true;
10287
        }
10288
10289
        if (false == $protocolFixApplied) {
10290
            if (false === strpos(api_get_path(WEB_PATH), $host)) {
10291
                // Check X-Frame-Options
10292
                $ch = curl_init();
10293
                $options = [
10294
                    CURLOPT_URL => $src,
10295
                    CURLOPT_RETURNTRANSFER => true,
10296
                    CURLOPT_HEADER => true,
10297
                    CURLOPT_FOLLOWLOCATION => true,
10298
                    CURLOPT_ENCODING => "",
10299
                    CURLOPT_AUTOREFERER => true,
10300
                    CURLOPT_CONNECTTIMEOUT => 120,
10301
                    CURLOPT_TIMEOUT => 120,
10302
                    CURLOPT_MAXREDIRS => 10,
10303
                ];
10304
10305
                $proxySettings = api_get_configuration_value('proxy_settings');
10306
                if (!empty($proxySettings) &&
10307
                    isset($proxySettings['curl_setopt_array'])
10308
                ) {
10309
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
10310
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
10311
                }
10312
10313
                curl_setopt_array($ch, $options);
10314
                $response = curl_exec($ch);
10315
                $httpCode = curl_getinfo($ch);
10316
                $headers = substr($response, 0, $httpCode['header_size']);
10317
10318
                $error = false;
10319
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
10320
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
10321
                ) {
10322
                    $error = true;
10323
                }
10324
10325
                if ($error) {
10326
                    Session::write('x_frame_source', $src);
10327
                    $src = 'blank.php?error=x_frames_options';
10328
                }
10329
            }
10330
        }
10331
10332
        return $src;
10333
    }
10334
10335
    /**
10336
     * Check if this LP has a created forum in the basis course.
10337
     *
10338
     * @return bool
10339
     */
10340
    public function lpHasForum()
10341
    {
10342
        $forumTable = Database::get_course_table(TABLE_FORUM);
10343
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
10344
10345
        $fakeFrom = "
10346
            $forumTable f
10347
            INNER JOIN $itemProperty ip
10348
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
10349
        ";
10350
10351
        $resultData = Database::select(
10352
            'COUNT(f.iid) AS qty',
10353
            $fakeFrom,
10354
            [
10355
                'where' => [
10356
                    'ip.visibility != ? AND ' => 2,
10357
                    'ip.tool = ? AND ' => TOOL_FORUM,
10358
                    'f.c_id = ? AND ' => intval($this->course_int_id),
10359
                    'f.lp_id = ?' => intval($this->lp_id),
10360
                ],
10361
            ],
10362
            'first'
10363
        );
10364
10365
        return $resultData['qty'] > 0;
10366
    }
10367
10368
    /**
10369
     * Get the forum for this learning path.
10370
     *
10371
     * @param int $sessionId
10372
     *
10373
     * @return array
10374
     */
10375
    public function getForum($sessionId = 0)
10376
    {
10377
        $repo = Container::getForumRepository();
10378
10379
        $course = api_get_course_entity();
10380
        $session = api_get_session_entity($sessionId);
10381
        $qb = $repo->getResourcesByCourse($course, $session);
10382
10383
        return $qb->getQuery()->getResult();
10384
    }
10385
10386
    /**
10387
     * Create a forum for this learning path.
10388
     *
10389
     * @return int The forum ID if was created. Otherwise return false
10390
     */
10391
    public function createForum(CForumCategory $forumCategory)
10392
    {
10393
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
10394
10395
        return store_forum(
10396
            [
10397
                'lp_id' => $this->lp_id,
10398
                'forum_title' => $this->name,
10399
                'forum_comment' => null,
10400
                'forum_category' => $forumCategory->getIid(),
10401
                'students_can_edit_group' => ['students_can_edit' => 0],
10402
                'allow_new_threads_group' => ['allow_new_threads' => 0],
10403
                'default_view_type_group' => ['default_view_type' => 'flat'],
10404
                'group_forum' => 0,
10405
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
10406
            ],
10407
            [],
10408
            true
10409
        );
10410
    }
10411
10412
    /**
10413
     * Get the LP Final Item form.
10414
     *
10415
     * @throws Exception
10416
     * @throws HTML_QuickForm_Error
10417
     *
10418
     * @return string
10419
     */
10420
    public function getFinalItemForm()
10421
    {
10422
        $finalItem = $this->getFinalItem();
10423
        $title = '';
10424
10425
        if ($finalItem) {
10426
            $title = $finalItem->get_title();
10427
            $buttonText = get_lang('Save');
10428
            $content = $this->getSavedFinalItem();
10429
        } else {
10430
            $buttonText = get_lang('Add this document to the course');
10431
            $content = $this->getFinalItemTemplate();
10432
        }
10433
10434
        $editorConfig = [
10435
            'ToolbarSet' => 'LearningPathDocuments',
10436
            'Width' => '100%',
10437
            'Height' => '500',
10438
            'FullPage' => true,
10439
//            'CreateDocumentDir' => $relative_prefix,
10440
    //            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
10441
  //          'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
10442
        ];
10443
10444
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
10445
            'type' => 'document',
10446
            'lp_id' => $this->lp_id,
10447
        ]);
10448
10449
        $form = new FormValidator('final_item', 'POST', $url);
10450
        $form->addText('title', get_lang('Title'));
10451
        $form->addButtonSave($buttonText);
10452
        $form->addHtml(
10453
            Display::return_message(
10454
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
10455
                'normal',
10456
                false
10457
            )
10458
        );
10459
10460
        $renderer = $form->defaultRenderer();
10461
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
10462
10463
        $form->addHtmlEditor(
10464
            'content_lp_certificate',
10465
            null,
10466
            true,
10467
            false,
10468
            $editorConfig,
10469
            true
10470
        );
10471
        $form->addHidden('action', 'add_final_item');
10472
        $form->addHidden('path', Session::read('pathItem'));
10473
        $form->addHidden('previous', $this->get_last());
10474
        $form->setDefaults(
10475
            ['title' => $title, 'content_lp_certificate' => $content]
10476
        );
10477
10478
        if ($form->validate()) {
10479
            $values = $form->exportValues();
10480
            $lastItemId = $this->getLastInFirstLevel();
10481
10482
            if (!$finalItem) {
10483
                $documentId = $this->create_document(
10484
                    $this->course_info,
10485
                    $values['content_lp_certificate'],
10486
                    $values['title']
10487
                );
10488
                $this->add_item(
10489
                    0,
10490
                    $lastItemId,
10491
                    'final_item',
10492
                    $documentId,
10493
                    $values['title'],
10494
                    ''
10495
                );
10496
10497
                Display::addFlash(
10498
                    Display::return_message(get_lang('Added'))
10499
                );
10500
            } else {
10501
                $this->edit_document($this->course_info);
10502
            }
10503
        }
10504
10505
        return $form->returnForm();
10506
    }
10507
10508
    /**
10509
     * Check if the current lp item is first, both, last or none from lp list.
10510
     *
10511
     * @param int $currentItemId
10512
     *
10513
     * @return string
10514
     */
10515
    public function isFirstOrLastItem($currentItemId)
10516
    {
10517
        $lpItemId = [];
10518
        $typeListNotToVerify = self::getChapterTypes();
10519
10520
        // Using get_toc() function instead $this->items because returns the correct order of the items
10521
        foreach ($this->get_toc() as $item) {
10522
            if (!in_array($item['type'], $typeListNotToVerify)) {
10523
                $lpItemId[] = $item['id'];
10524
            }
10525
        }
10526
10527
        $lastLpItemIndex = count($lpItemId) - 1;
10528
        $position = array_search($currentItemId, $lpItemId);
10529
10530
        switch ($position) {
10531
            case 0:
10532
                if (!$lastLpItemIndex) {
10533
                    $answer = 'both';
10534
                    break;
10535
                }
10536
10537
                $answer = 'first';
10538
                break;
10539
            case $lastLpItemIndex:
10540
                $answer = 'last';
10541
                break;
10542
            default:
10543
                $answer = 'none';
10544
        }
10545
10546
        return $answer;
10547
    }
10548
10549
    /**
10550
     * Get whether this is a learning path with the accumulated SCORM time or not.
10551
     *
10552
     * @return int
10553
     */
10554
    public function getAccumulateScormTime()
10555
    {
10556
        return $this->accumulateScormTime;
10557
    }
10558
10559
    /**
10560
     * Returns an HTML-formatted link to a resource, to incorporate directly into
10561
     * the new learning path tool.
10562
     *
10563
     * The function is a big switch on tool type.
10564
     * In each case, we query the corresponding table for information and build the link
10565
     * with that information.
10566
     *
10567
     * @author Yannick Warnier <[email protected]> - rebranding based on
10568
     * previous work (display_addedresource_link_in_learnpath())
10569
     *
10570
     * @param int $course_id      Course code
10571
     * @param int $learningPathId The learning path ID (in lp table)
10572
     * @param int $id_in_path     the unique index in the items table
10573
     * @param int $lpViewId
10574
     *
10575
     * @return string
10576
     */
10577
    public static function rl_get_resource_link_for_learnpath(
10578
        $course_id,
10579
        $learningPathId,
10580
        $id_in_path,
10581
        $lpViewId
10582
    ) {
10583
        $session_id = api_get_session_id();
10584
        $course_info = api_get_course_info_by_id($course_id);
10585
10586
        $learningPathId = (int) $learningPathId;
10587
        $id_in_path = (int) $id_in_path;
10588
        $lpViewId = (int) $lpViewId;
10589
10590
        $em = Database::getManager();
10591
        $lpItemRepo = $em->getRepository(CLpItem::class);
10592
10593
        /** @var CLpItem $rowItem */
10594
        $rowItem = $lpItemRepo->findOneBy([
10595
            'cId' => $course_id,
10596
            'lp' => $learningPathId,
10597
            'iid' => $id_in_path,
10598
        ]);
10599
10600
        if (!$rowItem) {
10601
            // Try one more time with "id"
10602
            /** @var CLpItem $rowItem */
10603
            $rowItem = $lpItemRepo->findOneBy([
10604
                'cId' => $course_id,
10605
                'lp' => $learningPathId,
10606
                'id' => $id_in_path,
10607
            ]);
10608
10609
            if (!$rowItem) {
10610
                return -1;
10611
            }
10612
        }
10613
10614
        $type = $rowItem->getItemType();
10615
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
10616
        $main_dir_path = api_get_path(WEB_CODE_PATH);
10617
        //$main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
10618
        $link = '';
10619
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
10620
10621
        switch ($type) {
10622
            case 'dir':
10623
                return $main_dir_path.'lp/blank.php';
10624
            case TOOL_CALENDAR_EVENT:
10625
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
10626
            case TOOL_ANNOUNCEMENT:
10627
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
10628
            case TOOL_LINK:
10629
                $linkInfo = Link::getLinkInfo($id);
10630
                if (isset($linkInfo['url'])) {
10631
                    return $linkInfo['url'];
10632
                }
10633
10634
                return '';
10635
            case TOOL_QUIZ:
10636
                if (empty($id)) {
10637
                    return '';
10638
                }
10639
10640
                // Get the lp_item_view with the highest view_count.
10641
                $learnpathItemViewResult = $em
10642
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
10643
                    ->findBy(
10644
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
10645
                        ['viewCount' => 'DESC'],
10646
                        1
10647
                    );
10648
                /** @var CLpItemView $learnpathItemViewData */
10649
                $learnpathItemViewData = current($learnpathItemViewResult);
10650
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
10651
10652
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
10653
                    .http_build_query([
10654
                        'lp_init' => 1,
10655
                        'learnpath_item_view_id' => $learnpathItemViewId,
10656
                        'learnpath_id' => $learningPathId,
10657
                        'learnpath_item_id' => $id_in_path,
10658
                        'exerciseId' => $id,
10659
                    ]);
10660
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
10661
                /*$TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
10662
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
10663
                $myrow = Database::fetch_array($result);
10664
                $path = $myrow['path'];
10665
10666
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
10667
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
10668
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;*/
10669
            case TOOL_FORUM:
10670
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
10671
            case TOOL_THREAD:
10672
                // forum post
10673
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
10674
                if (empty($id)) {
10675
                    return '';
10676
                }
10677
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
10678
                $result = Database::query($sql);
10679
                $myrow = Database::fetch_array($result);
10680
10681
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
10682
                    .$extraParams;
10683
            case TOOL_POST:
10684
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
10685
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
10686
                $myrow = Database::fetch_array($result);
10687
10688
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
10689
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
10690
            case TOOL_READOUT_TEXT:
10691
                return api_get_path(WEB_CODE_PATH).
10692
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
10693
            case TOOL_DOCUMENT:
10694
                $repo = Container::getDocumentRepository();
10695
                $document = $repo->find($rowItem->getPath());
10696
                $file = $repo->getResourceFileUrl($document, [], UrlGeneratorInterface::ABSOLUTE_URL);
10697
10698
                return $file;
10699
10700
                $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...
10701
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v'];
10702
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
10703
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
10704
10705
                $openmethod = 2;
10706
                $officedoc = false;
10707
                Session::write('openmethod', $openmethod);
10708
                Session::write('officedoc', $officedoc);
10709
10710
                if ($showDirectUrl) {
10711
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
10712
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
10713
                        if (Link::isPdfLink($file)) {
10714
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
10715
10716
                            return $pdfUrl;
10717
                        }
10718
                    }
10719
10720
                    return $file;
10721
                }
10722
10723
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
10724
            case TOOL_LP_FINAL_ITEM:
10725
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
10726
                    .$extraParams;
10727
            case 'assignments':
10728
                return $main_dir_path.'work/work.php?'.$extraParams;
10729
            case TOOL_DROPBOX:
10730
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
10731
            case 'introduction_text': //DEPRECATED
10732
                return '';
10733
            case TOOL_COURSE_DESCRIPTION:
10734
                return $main_dir_path.'course_description?'.$extraParams;
10735
            case TOOL_GROUP:
10736
                return $main_dir_path.'group/group.php?'.$extraParams;
10737
            case TOOL_USER:
10738
                return $main_dir_path.'user/user.php?'.$extraParams;
10739
            case TOOL_STUDENTPUBLICATION:
10740
                if (!empty($rowItem->getPath())) {
10741
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
10742
                }
10743
10744
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
10745
        }
10746
10747
        return $link;
10748
    }
10749
10750
    /**
10751
     * Gets the name of a resource (generally used in learnpath when no name is provided).
10752
     *
10753
     * @author Yannick Warnier <[email protected]>
10754
     *
10755
     * @param string $course_code    Course code
10756
     * @param int    $learningPathId
10757
     * @param int    $id_in_path     The resource ID
10758
     *
10759
     * @return string
10760
     */
10761
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
10762
    {
10763
        $_course = api_get_course_info($course_code);
10764
        if (empty($_course)) {
10765
            return '';
10766
        }
10767
        $course_id = $_course['real_id'];
10768
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10769
        $learningPathId = (int) $learningPathId;
10770
        $id_in_path = (int) $id_in_path;
10771
10772
        $sql = "SELECT item_type, title, ref
10773
                FROM $tbl_lp_item
10774
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
10775
        $res_item = Database::query($sql);
10776
10777
        if (Database::num_rows($res_item) < 1) {
10778
            return '';
10779
        }
10780
        $row_item = Database::fetch_array($res_item);
10781
        $type = strtolower($row_item['item_type']);
10782
        $id = $row_item['ref'];
10783
        $output = '';
10784
10785
        switch ($type) {
10786
            case TOOL_CALENDAR_EVENT:
10787
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
10788
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
10789
                $myrow = Database::fetch_array($result);
10790
                $output = $myrow['title'];
10791
                break;
10792
            case TOOL_ANNOUNCEMENT:
10793
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
10794
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
10795
                $myrow = Database::fetch_array($result);
10796
                $output = $myrow['title'];
10797
                break;
10798
            case TOOL_LINK:
10799
                // Doesn't take $target into account.
10800
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
10801
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
10802
                $myrow = Database::fetch_array($result);
10803
                $output = $myrow['title'];
10804
                break;
10805
            case TOOL_QUIZ:
10806
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
10807
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
10808
                $myrow = Database::fetch_array($result);
10809
                $output = $myrow['title'];
10810
                break;
10811
            case TOOL_FORUM:
10812
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
10813
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
10814
                $myrow = Database::fetch_array($result);
10815
                $output = $myrow['forum_name'];
10816
                break;
10817
            case TOOL_THREAD:
10818
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
10819
                // Grabbing the title of the post.
10820
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
10821
                $result_title = Database::query($sql_title);
10822
                $myrow_title = Database::fetch_array($result_title);
10823
                $output = $myrow_title['post_title'];
10824
                break;
10825
            case TOOL_POST:
10826
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
10827
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
10828
                $result = Database::query($sql);
10829
                $post = Database::fetch_array($result);
10830
                $output = $post['post_title'];
10831
                break;
10832
            case 'dir':
10833
            case TOOL_DOCUMENT:
10834
                $title = $row_item['title'];
10835
                $output = '-';
10836
                if (!empty($title)) {
10837
                    $output = $title;
10838
                }
10839
                break;
10840
            case 'hotpotatoes':
10841
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
10842
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
10843
                $myrow = Database::fetch_array($result);
10844
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
10845
                $last = count($pathname) - 1; // Making a correct name for the link.
10846
                $filename = $pathname[$last]; // Making a correct name for the link.
10847
                $myrow['path'] = rawurlencode($myrow['path']);
10848
                $output = $filename;
10849
                break;
10850
        }
10851
10852
        return stripslashes($output);
10853
    }
10854
10855
    /**
10856
     * Get the parent names for the current item.
10857
     *
10858
     * @param int $newItemId Optional. The item ID
10859
     *
10860
     * @return array
10861
     */
10862
    public function getCurrentItemParentNames($newItemId = 0)
10863
    {
10864
        $newItemId = $newItemId ?: $this->get_current_item_id();
10865
        $return = [];
10866
        $item = $this->getItem($newItemId);
10867
        $parent = $this->getItem($item->get_parent());
10868
10869
        while ($parent) {
10870
            $return[] = $parent->get_title();
10871
            $parent = $this->getItem($parent->get_parent());
10872
        }
10873
10874
        return array_reverse($return);
10875
    }
10876
10877
    /**
10878
     * Reads and process "lp_subscription_settings" setting.
10879
     *
10880
     * @return array
10881
     */
10882
    public static function getSubscriptionSettings()
10883
    {
10884
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
10885
        if (empty($subscriptionSettings)) {
10886
            // By default allow both settings
10887
            $subscriptionSettings = [
10888
                'allow_add_users_to_lp' => true,
10889
                'allow_add_users_to_lp_category' => true,
10890
            ];
10891
        } else {
10892
            $subscriptionSettings = $subscriptionSettings['options'];
10893
        }
10894
10895
        return $subscriptionSettings;
10896
    }
10897
10898
    /**
10899
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
10900
     */
10901
    public function exportToCourseBuildFormat()
10902
    {
10903
        if (!api_is_allowed_to_edit()) {
10904
            return false;
10905
        }
10906
10907
        $courseBuilder = new CourseBuilder();
10908
        $itemList = [];
10909
        /** @var learnpathItem $item */
10910
        foreach ($this->items as $item) {
10911
            $itemList[$item->get_type()][] = $item->get_path();
10912
        }
10913
10914
        if (empty($itemList)) {
10915
            return false;
10916
        }
10917
10918
        if (isset($itemList['document'])) {
10919
            // Get parents
10920
            foreach ($itemList['document'] as $documentId) {
10921
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
10922
                if (!empty($documentInfo['parents'])) {
10923
                    foreach ($documentInfo['parents'] as $parentInfo) {
10924
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
10925
                            continue;
10926
                        }
10927
                        $itemList['document'][] = $parentInfo['iid'];
10928
                    }
10929
                }
10930
            }
10931
10932
            $courseInfo = api_get_course_info();
10933
            foreach ($itemList['document'] as $documentId) {
10934
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
10935
                $items = DocumentManager::get_resources_from_source_html(
10936
                    $documentInfo['absolute_path'],
10937
                    true,
10938
                    TOOL_DOCUMENT
10939
                );
10940
10941
                if (!empty($items)) {
10942
                    foreach ($items as $item) {
10943
                        // Get information about source url
10944
                        $url = $item[0]; // url
10945
                        $scope = $item[1]; // scope (local, remote)
10946
                        $type = $item[2]; // type (rel, abs, url)
10947
10948
                        $origParseUrl = parse_url($url);
10949
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
10950
10951
                        if ('local' == $scope) {
10952
                            if ('abs' == $type || 'rel' == $type) {
10953
                                $documentFile = strstr($realOrigPath, 'document');
10954
                                if (false !== strpos($realOrigPath, $documentFile)) {
10955
                                    $documentFile = str_replace('document', '', $documentFile);
10956
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
10957
                                    // Document found! Add it to the list
10958
                                    if ($itemDocumentId) {
10959
                                        $itemList['document'][] = $itemDocumentId;
10960
                                    }
10961
                                }
10962
                            }
10963
                        }
10964
                    }
10965
                }
10966
            }
10967
10968
            $courseBuilder->build_documents(
10969
                api_get_session_id(),
10970
                $this->get_course_int_id(),
10971
                true,
10972
                $itemList['document']
10973
            );
10974
        }
10975
10976
        if (isset($itemList['quiz'])) {
10977
            $courseBuilder->build_quizzes(
10978
                api_get_session_id(),
10979
                $this->get_course_int_id(),
10980
                true,
10981
                $itemList['quiz']
10982
            );
10983
        }
10984
10985
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
10986
10987
        /*if (!empty($itemList['thread'])) {
10988
            $postList = [];
10989
            foreach ($itemList['thread'] as $postId) {
10990
                $post = get_post_information($postId);
10991
                if ($post) {
10992
                    if (!isset($itemList['forum'])) {
10993
                        $itemList['forum'] = [];
10994
                    }
10995
                    $itemList['forum'][] = $post['forum_id'];
10996
                    $postList[] = $postId;
10997
                }
10998
            }
10999
11000
            if (!empty($postList)) {
11001
                $courseBuilder->build_forum_posts(
11002
                    $this->get_course_int_id(),
11003
                    null,
11004
                    null,
11005
                    $postList
11006
                );
11007
            }
11008
        }*/
11009
11010
        if (!empty($itemList['thread'])) {
11011
            $threadList = [];
11012
            $em = Database::getManager();
11013
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
11014
            foreach ($itemList['thread'] as $threadId) {
11015
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
11016
                $thread = $repo->find($threadId);
11017
                if ($thread) {
11018
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
11019
                    $threadList[] = $thread->getIid();
11020
                }
11021
            }
11022
11023
            if (!empty($threadList)) {
11024
                $courseBuilder->build_forum_topics(
11025
                    api_get_session_id(),
11026
                    $this->get_course_int_id(),
11027
                    null,
11028
                    $threadList
11029
                );
11030
            }
11031
        }
11032
11033
        $forumCategoryList = [];
11034
        if (isset($itemList['forum'])) {
11035
            foreach ($itemList['forum'] as $forumId) {
11036
                $forumInfo = get_forums($forumId);
11037
                $forumCategoryList[] = $forumInfo['forum_category'];
11038
            }
11039
        }
11040
11041
        if (!empty($forumCategoryList)) {
11042
            $courseBuilder->build_forum_category(
11043
                api_get_session_id(),
11044
                $this->get_course_int_id(),
11045
                true,
11046
                $forumCategoryList
11047
            );
11048
        }
11049
11050
        if (!empty($itemList['forum'])) {
11051
            $courseBuilder->build_forums(
11052
                api_get_session_id(),
11053
                $this->get_course_int_id(),
11054
                true,
11055
                $itemList['forum']
11056
            );
11057
        }
11058
11059
        if (isset($itemList['link'])) {
11060
            $courseBuilder->build_links(
11061
                api_get_session_id(),
11062
                $this->get_course_int_id(),
11063
                true,
11064
                $itemList['link']
11065
            );
11066
        }
11067
11068
        if (!empty($itemList['student_publication'])) {
11069
            $courseBuilder->build_works(
11070
                api_get_session_id(),
11071
                $this->get_course_int_id(),
11072
                true,
11073
                $itemList['student_publication']
11074
            );
11075
        }
11076
11077
        $courseBuilder->build_learnpaths(
11078
            api_get_session_id(),
11079
            $this->get_course_int_id(),
11080
            true,
11081
            [$this->get_id()],
11082
            false
11083
        );
11084
11085
        $courseBuilder->restoreDocumentsFromList();
11086
11087
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
11088
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
11089
        $result = DocumentManager::file_send_for_download(
11090
            $zipPath,
11091
            true,
11092
            $this->get_name().'.zip'
11093
        );
11094
11095
        if ($result) {
11096
            api_not_allowed();
11097
        }
11098
11099
        return true;
11100
    }
11101
11102
    /**
11103
     * Get whether this is a learning path with the accumulated work time or not.
11104
     *
11105
     * @return int
11106
     */
11107
    public function getAccumulateWorkTime()
11108
    {
11109
        return (int) $this->accumulateWorkTime;
11110
    }
11111
11112
    /**
11113
     * Get whether this is a learning path with the accumulated work time or not.
11114
     *
11115
     * @return int
11116
     */
11117
    public function getAccumulateWorkTimeTotalCourse()
11118
    {
11119
        $table = Database::get_course_table(TABLE_LP_MAIN);
11120
        $sql = "SELECT SUM(accumulate_work_time) AS total
11121
                FROM $table
11122
                WHERE c_id = ".$this->course_int_id;
11123
        $result = Database::query($sql);
11124
        $row = Database::fetch_array($result);
11125
11126
        return (int) $row['total'];
11127
    }
11128
11129
    /**
11130
     * @param int $lpId
11131
     * @param int $courseId
11132
     *
11133
     * @return mixed
11134
     */
11135
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
11136
    {
11137
        $lpId = (int) $lpId;
11138
        $courseId = (int) $courseId;
11139
11140
        $table = Database::get_course_table(TABLE_LP_MAIN);
11141
        $sql = "SELECT accumulate_work_time
11142
                FROM $table
11143
                WHERE c_id = $courseId AND id = $lpId";
11144
        $result = Database::query($sql);
11145
        $row = Database::fetch_array($result);
11146
11147
        return $row['accumulate_work_time'];
11148
    }
11149
11150
    /**
11151
     * @param int $courseId
11152
     *
11153
     * @return int
11154
     */
11155
    public static function getAccumulateWorkTimeTotal($courseId)
11156
    {
11157
        $table = Database::get_course_table(TABLE_LP_MAIN);
11158
        $courseId = (int) $courseId;
11159
        $sql = "SELECT SUM(accumulate_work_time) AS total
11160
                FROM $table
11161
                WHERE c_id = $courseId";
11162
        $result = Database::query($sql);
11163
        $row = Database::fetch_array($result);
11164
11165
        return (int) $row['total'];
11166
    }
11167
11168
    /**
11169
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
11170
     * and put the images in.
11171
     *
11172
     * @return array
11173
     */
11174
    public static function getIconSelect()
11175
    {
11176
        $theme = api_get_visual_theme();
11177
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
11178
        $icons = ['' => get_lang('Please select an option')];
11179
11180
        if (is_dir($path)) {
11181
            $finder = new Finder();
11182
            $finder->files()->in($path);
11183
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
11184
            /** @var SplFileInfo $file */
11185
            foreach ($finder as $file) {
11186
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
11187
                    $icons[$file->getFilename()] = $file->getFilename();
11188
                }
11189
            }
11190
        }
11191
11192
        return $icons;
11193
    }
11194
11195
    /**
11196
     * @param int $lpId
11197
     *
11198
     * @return string
11199
     */
11200
    public static function getSelectedIcon($lpId)
11201
    {
11202
        $extraFieldValue = new ExtraFieldValue('lp');
11203
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
11204
        $icon = '';
11205
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
11206
            $icon = $lpIcon['value'];
11207
        }
11208
11209
        return $icon;
11210
    }
11211
11212
    /**
11213
     * @param int $lpId
11214
     *
11215
     * @return string
11216
     */
11217
    public static function getSelectedIconHtml($lpId)
11218
    {
11219
        $icon = self::getSelectedIcon($lpId);
11220
11221
        if (empty($icon)) {
11222
            return '';
11223
        }
11224
11225
        $theme = api_get_visual_theme();
11226
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
11227
11228
        return Display::img($path);
11229
    }
11230
11231
    /**
11232
     * @param string $value
11233
     *
11234
     * @return string
11235
     */
11236
    public function cleanItemTitle($value)
11237
    {
11238
        $value = Security::remove_XSS(strip_tags($value));
11239
11240
        return $value;
11241
    }
11242
11243
    public function setItemTitle(FormValidator $form)
11244
    {
11245
        if (api_get_configuration_value('save_titles_as_html')) {
11246
            $form->addHtmlEditor(
11247
                'title',
11248
                get_lang('Title'),
11249
                true,
11250
                false,
11251
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
11252
            );
11253
        } else {
11254
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
11255
            $form->applyFilter('title', 'trim');
11256
            $form->applyFilter('title', 'html_filter');
11257
        }
11258
    }
11259
11260
    /**
11261
     * @return array
11262
     */
11263
    public function getItemsForForm($addParentCondition = false)
11264
    {
11265
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11266
        $course_id = api_get_course_int_id();
11267
11268
        $sql = "SELECT * FROM $tbl_lp_item
11269
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
11270
11271
        if ($addParentCondition) {
11272
            $sql .= ' AND parent_item_id = 0 ';
11273
        }
11274
        $sql .= ' ORDER BY display_order ASC';
11275
11276
        $result = Database::query($sql);
11277
        $arrLP = [];
11278
        while ($row = Database::fetch_array($result)) {
11279
            $arrLP[] = [
11280
                'iid' => $row['iid'],
11281
                'id' => $row['iid'],
11282
                'item_type' => $row['item_type'],
11283
                'title' => $this->cleanItemTitle($row['title']),
11284
                'title_raw' => $row['title'],
11285
                'path' => $row['path'],
11286
                'description' => Security::remove_XSS($row['description']),
11287
                'parent_item_id' => $row['parent_item_id'],
11288
                'previous_item_id' => $row['previous_item_id'],
11289
                'next_item_id' => $row['next_item_id'],
11290
                'display_order' => $row['display_order'],
11291
                'max_score' => $row['max_score'],
11292
                'min_score' => $row['min_score'],
11293
                'mastery_score' => $row['mastery_score'],
11294
                'prerequisite' => $row['prerequisite'],
11295
                'max_time_allowed' => $row['max_time_allowed'],
11296
                'prerequisite_min_score' => $row['prerequisite_min_score'],
11297
                'prerequisite_max_score' => $row['prerequisite_max_score'],
11298
            ];
11299
        }
11300
11301
        return $arrLP;
11302
    }
11303
11304
    /**
11305
     * Gets whether this SCORM learning path has been marked to use the score
11306
     * as progress. Takes into account whether the learnpath matches (SCORM
11307
     * content + less than 2 items).
11308
     *
11309
     * @return bool True if the score should be used as progress, false otherwise
11310
     */
11311
    public function getUseScoreAsProgress()
11312
    {
11313
        // If not a SCORM, we don't care about the setting
11314
        if (2 != $this->get_type()) {
11315
            return false;
11316
        }
11317
        // If more than one step in the SCORM, we don't care about the setting
11318
        if ($this->get_total_items_count() > 1) {
11319
            return false;
11320
        }
11321
        $extraFieldValue = new ExtraFieldValue('lp');
11322
        $doUseScore = false;
11323
        $useScore = $extraFieldValue->get_values_by_handler_and_field_variable($this->get_id(), 'use_score_as_progress');
11324
        if (!empty($useScore) && isset($useScore['value'])) {
11325
            $doUseScore = $useScore['value'];
11326
        }
11327
11328
        return $doUseScore;
11329
    }
11330
11331
    /**
11332
     * Get the user identifier (user_id or username
11333
     * Depends on scorm_api_username_as_student_id in app/config/configuration.php.
11334
     *
11335
     * @return string User ID or username, depending on configuration setting
11336
     */
11337
    public static function getUserIdentifierForExternalServices()
11338
    {
11339
        if (api_get_configuration_value('scorm_api_username_as_student_id')) {
11340
            return api_get_user_info(api_get_user_id())['username'];
11341
        } elseif (null != api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id')) {
11342
            $extraFieldValue = new ExtraFieldValue('user');
11343
            $extrafield = $extraFieldValue->get_values_by_handler_and_field_variable(api_get_user_id(), api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id'));
11344
11345
            return $extrafield['value'];
11346
        } else {
11347
            return api_get_user_id();
11348
        }
11349
    }
11350
11351
    /**
11352
     * Save the new order for learning path items.
11353
     *
11354
     * We have to update parent_item_id, previous_item_id, next_item_id, display_order in the database.
11355
     *
11356
     * @param array $orderList A associative array with item ID as key and parent ID as value.
11357
     * @param int   $courseId
11358
     */
11359
    public static function sortItemByOrderList(array $orderList, $courseId = 0)
11360
    {
11361
        $courseId = $courseId ?: api_get_course_int_id();
11362
        $itemList = new LpItemOrderList();
11363
11364
        foreach ($orderList as $id => $parentId) {
11365
            $item = new LpOrderItem($id, $parentId);
11366
            $itemList->add($item);
11367
        }
11368
11369
        $parents = $itemList->getListOfParents();
11370
11371
        foreach ($parents as $parentId) {
11372
            $sameParentLpItemList = $itemList->getItemWithSameParent($parentId);
11373
            $previous_item_id = 0;
11374
            for ($i = 0; $i < count($sameParentLpItemList->list); $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...
11375
                $item_id = $sameParentLpItemList->list[$i]->id;
11376
                // display_order
11377
                $display_order = $i + 1;
11378
                $itemList->setParametersForId($item_id, $display_order, 'display_order');
11379
                // previous_item_id
11380
                $itemList->setParametersForId($item_id, $previous_item_id, 'previous_item_id');
11381
                $previous_item_id = $item_id;
11382
                // next_item_id
11383
                $next_item_id = 0;
11384
                if ($i < count($sameParentLpItemList->list) - 1) {
11385
                    $next_item_id = $sameParentLpItemList->list[$i + 1]->id;
11386
                }
11387
                $itemList->setParametersForId($item_id, $next_item_id, 'next_item_id');
11388
            }
11389
        }
11390
11391
        $table = Database::get_course_table(TABLE_LP_ITEM);
11392
11393
        foreach ($itemList->list as $item) {
11394
            $params = [];
11395
            $params['display_order'] = $item->display_order;
11396
            $params['previous_item_id'] = $item->previous_item_id;
11397
            $params['next_item_id'] = $item->next_item_id;
11398
            $params['parent_item_id'] = $item->parent_item_id;
11399
11400
            Database::update(
11401
                $table,
11402
                $params,
11403
                [
11404
                    'iid = ? AND c_id = ? ' => [
11405
                        (int) $item->id,
11406
                        (int) $courseId,
11407
                    ],
11408
                ]
11409
            );
11410
        }
11411
    }
11412
11413
    /**
11414
     * Get the depth level of LP item.
11415
     *
11416
     * @param array $items
11417
     * @param int   $currentItemId
11418
     *
11419
     * @return int
11420
     */
11421
    private static function get_level_for_item($items, $currentItemId)
11422
    {
11423
        $parentItemId = 0;
11424
        if (isset($items[$currentItemId])) {
11425
            $parentItemId = $items[$currentItemId]->parent;
11426
        }
11427
11428
        if (0 == $parentItemId) {
11429
            return 0;
11430
        } else {
11431
            return self::get_level_for_item($items, $parentItemId) + 1;
11432
        }
11433
    }
11434
11435
    /**
11436
     * Generate the link for a learnpath category as course tool.
11437
     *
11438
     * @param int $categoryId
11439
     *
11440
     * @return string
11441
     */
11442
    private static function getCategoryLinkForTool($categoryId)
11443
    {
11444
        $categoryId = (int) $categoryId;
11445
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
11446
            .http_build_query(
11447
                [
11448
                    'action' => 'view_category',
11449
                    'id' => $categoryId,
11450
                ]
11451
            );
11452
11453
        return $link;
11454
    }
11455
11456
    /**
11457
     * Return the scorm item type object with spaces replaced with _
11458
     * The return result is use to build a css classname like scorm_type_$return.
11459
     *
11460
     * @param $in_type
11461
     *
11462
     * @return mixed
11463
     */
11464
    private static function format_scorm_type_item($in_type)
11465
    {
11466
        return str_replace(' ', '_', $in_type);
11467
    }
11468
11469
    /**
11470
     * Check and obtain the lp final item if exist.
11471
     *
11472
     * @return learnpathItem
11473
     */
11474
    private function getFinalItem()
11475
    {
11476
        if (empty($this->items)) {
11477
            return null;
11478
        }
11479
11480
        foreach ($this->items as $item) {
11481
            if ('final_item' !== $item->type) {
11482
                continue;
11483
            }
11484
11485
            return $item;
11486
        }
11487
    }
11488
11489
    /**
11490
     * Get the LP Final Item Template.
11491
     *
11492
     * @return string
11493
     */
11494
    private function getFinalItemTemplate()
11495
    {
11496
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
11497
    }
11498
11499
    /**
11500
     * Get the LP Final Item Url.
11501
     *
11502
     * @return string
11503
     */
11504
    private function getSavedFinalItem()
11505
    {
11506
        $finalItem = $this->getFinalItem();
11507
11508
        $repo = Container::getDocumentRepository();
11509
        /** @var CDocument $document */
11510
        $document = $repo->find($finalItem->path);
11511
11512
        if ($document && $document->getResourceNode()->hasResourceFile()) {
11513
            return  $repo->getResourceFileContent($document);
11514
        }
11515
11516
        return '';
11517
    }
11518
}
11519