Passed
Push — master ( e80e7a...4357b6 )
by Julito
11:11 queued 02:04
created

learnpath   F

Complexity

Total Complexity 1470

Size/Duplication

Total Lines 11960
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 6184
c 0
b 0
f 0
dl 0
loc 11960
rs 0.8
wmc 1470

226 Methods

Rating   Name   Duplication   Size   Complexity  
A get_course_int_id() 0 3 2
A set_course_int_id() 0 3 1
A getCourseCode() 0 3 1
A getEntity() 0 3 1
F add_item() 0 211 16
A close() 0 13 2
F autocomplete_parents() 0 101 17
F add_lp() 0 140 15
A getProgressBar() 0 5 1
A next() 0 22 5
B get_iv_objectives_array() 0 38 6
A togglePublish() 0 22 4
A get_total_items_count() 0 3 1
A get_first_item_id() 0 8 2
A get_update_queue() 0 3 1
F get_link() 0 308 51
D prerequisites_match() 0 69 16
A previous() 0 11 1
B move_down() 0 54 8
A open() 0 9 1
F edit_item() 0 212 22
B getChildrenToc() 0 49 11
A get_author() 0 7 2
A get_last() 0 10 2
A getHideTableOfContents() 0 3 1
A categoryIsPublished() 0 25 2
A get_type() 0 8 3
A get_maker() 0 7 2
A get_progress_bar() 0 12 1
A getLpNameById() 0 8 1
A get_type_static() 0 16 3
A get_items_status_list() 0 10 2
A get_theme() 0 7 2
A get_previous_index() 0 16 5
A get_preview_image() 0 7 2
C isBlockedByPrerequisite() 0 68 13
B get_progress_bar_text() 0 56 11
A get_teacher_toc_buttons() 0 21 4
A get_js_lib() 0 8 2
B getTOCTree() 0 44 8
F first() 0 71 20
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 get_current_item_id() 0 8 2
A save_current() 0 32 6
A getLastInFirstLevel() 0 13 2
A getChapterTypes() 0 4 1
B get_preview_image_path() 0 28 7
B restart() 0 38 6
B get_iv_interactions_array() 0 54 8
A getNameNoTags() 0 3 1
C get_mediaplayer() 0 80 13
A getProgressFromLpList() 0 32 4
B move_up() 0 51 8
A get_interactions_count_from_db() 0 16 2
B get_scorm_prereq_string() 0 73 11
B get_scorm_xml_node() 0 19 7
A has_audio() 0 11 3
B edit_item_prereq() 0 40 7
A get_id() 0 7 2
A get_next_item_id() 0 10 3
B get_view() 0 46 7
F is_lp_visible_for_student() 0 141 25
A get_flat_ordered_items_list() 0 35 5
A getProgress() 0 23 2
A get_complete_items_count() 0 24 5
A get_objectives_count_from_db() 0 16 2
B delete_item() 0 70 6
D move_item() 0 125 18
A getTotalItemsCountWithoutDirs() 0 11 3
A toggleCategoryVisibility() 0 19 3
C getParentToc() 0 54 13
D getListArrayToc() 0 67 11
A get_view_id() 0 7 2
C toggleCategoryPublish() 0 103 12
C get_navigation_bar() 0 73 10
B get_next_index() 0 23 7
D getPackageType() 0 90 20
A toggleVisibility() 0 18 3
A get_common_index_terms_by_prefix() 0 17 3
A get_lp_session_id() 0 7 2
A get_previous_item_id() 0 5 1
A get_toc() 0 18 2
F categoryIsVisibleForStudent() 0 89 19
F delete() 0 125 19
A get_name() 0 7 2
A getStatusCSSClassName() 0 7 2
A delete_children_items() 0 22 4
B save_item() 0 47 9
F __construct() 0 271 39
B update_default_view_mode() 0 33 6
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 copy() 0 26 1
A displayDocumentForm() 0 46 4
A getCategoryFromCourseIntoSelect() 0 15 4
A switch_attempt_mode() 0 16 4
A getAccumulateScormTime() 0 3 1
F create_document() 0 146 26
A tree_array() 0 4 1
A createCategory() 0 24 1
C get_exercises() 0 131 11
C fixBlockedLinks() 0 64 11
A createForum() 0 18 1
A set_jslib() 0 15 2
A update_default_scorm_commit() 0 25 4
A getLpList() 0 9 1
A getCategorySessionId() 0 18 3
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 set_publicated_on() 0 22 3
A getUserIdentifierForExternalServices() 0 11 3
B set_current_item() 0 33 10
A getAccumulateWorkTime() 0 3 1
A set_prerequisite() 0 10 1
A display_lp_prerequisites_list() 0 34 5
A set_modified_on() 0 10 1
A display_link_form() 0 22 2
A getForum() 0 9 1
A set_preview_image() 0 11 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
F createReadOutText() 0 121 27
A moveDownCategory() 0 11 2
A sort_tree_array() 0 12 3
F scormExport() 0 973 114
A getAccumulateWorkTimePrerequisite() 0 13 1
A getSelectedIconHtml() 0 12 2
A display_item_form() 0 14 1
A select_previous_item_id() 0 22 1
A getItemsForForm() 0 39 3
A set_seriousgame_mode() 0 23 4
A returnLpItemList() 0 15 2
A getItem() 0 7 3
A getCategoryId() 0 3 1
B create_tree_array() 0 38 11
B get_links() 0 118 7
B set_terms_by_prefix() 0 68 10
A set_use_max_score() 0 12 1
A create_path() 0 14 5
A set_previous_item() 0 6 2
A getCurrentBuildingModeURL() 0 11 5
A display_document() 0 26 3
B upload_image() 0 40 6
A setSubscribeUsers() 0 10 1
A set_theme() 0 11 1
C rl_get_resource_name() 0 92 14
A displayNewSectionForm() 0 17 1
A getSavedFinalItem() 0 13 3
A set_author() 0 10 1
A cleanItemTitle() 0 5 1
A format_scorm_type_item() 0 3 1
A getCurrentItemParentNames() 0 13 3
A getSubscribeUsers() 0 3 1
A set_attempt_mode() 0 32 5
A deleteCategory() 0 22 3
A update_display_order() 0 29 5
B generate_lp_folder() 0 52 7
A getAccumulateWorkTimeTotalCourse() 0 10 1
A display_quiz_form() 0 13 1
A moveUpCategory() 0 11 2
A set_proximity() 0 15 2
C display_item() 0 81 14
B overview() 0 50 9
A set_expired_on() 0 23 3
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
A set_encoding() 0 19 4
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 getCategoryByCourse() 0 8 1
A getCategoryLinkForTool() 0 12 1
A getSubscriptionSettings() 0 14 2
B verify_document_size() 0 25 8
B get_documents() 0 110 2
B get_forums() 0 112 9
A edit_document() 0 14 4
A getCurrentAttempt() 0 8 2
A delete_lp_image() 0 17 5
A display_forum_form() 0 16 2
A update_scorm_debug() 0 23 4
A getUseScoreAsProgress() 0 18 5
A getFinalItem() 0 12 4
A displayResources() 0 55 2
A setAccumulateScormTime() 0 11 1
A update_reinit() 0 23 4
A getFinalItemTemplate() 0 3 1
A get_extension() 0 5 1
F rl_get_resource_link_for_learnpath() 0 171 32
A getAccumulateWorkTimeTotal() 0 11 1
A getLpFromSession() 0 28 5
A generate_learning_path_folder() 0 24 3
A set_hide_toc_frame() 0 15 2
A getCategories() 0 9 1
B get_attempt_mode() 0 21 9
A return_new_tree() 0 34 4
A setCategoryId() 0 18 2
A getIconSelect() 0 19 4
F exportToCourseBuildFormat() 0 199 31
A get_level_for_item() 0 11 3
A set_maker() 0 14 2
A get_student_publications() 0 42 3
A lpHasForum() 0 26 1
A display_student_publication_form() 0 15 1
A setAccumulateWorkTime() 0 14 2
A set_name() 0 37 2
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\CLink;
13
use Chamilo\CourseBundle\Entity\CLp;
14
use Chamilo\CourseBundle\Entity\CLpCategory;
15
use Chamilo\CourseBundle\Entity\CLpItem;
16
use Chamilo\CourseBundle\Entity\CLpItemView;
17
use Chamilo\CourseBundle\Entity\CQuiz;
18
use Chamilo\CourseBundle\Entity\CStudentPublication;
19
use Chamilo\CourseBundle\Entity\CTool;
20
use ChamiloSession as Session;
21
use Gedmo\Sortable\Entity\Repository\SortableRepository;
22
use Symfony\Component\Filesystem\Filesystem;
23
use Symfony\Component\Finder\Finder;
24
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
25
26
/**
27
 * Class learnpath
28
 * This class defines the parent attributes and methods for Chamilo learnpaths
29
 * and SCORM learnpaths. It is used by the scorm class.
30
 *
31
 * @todo decouple class
32
 *
33
 * @author  Yannick Warnier <[email protected]>
34
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
35
 */
36
class learnpath
37
{
38
    public const MAX_LP_ITEM_TITLE_LENGTH = 32;
39
    public const STATUS_CSS_CLASS_NAME = [
40
        'not attempted' => 'scorm_not_attempted',
41
        'incomplete' => 'scorm_not_attempted',
42
        'failed' => 'scorm_failed',
43
        'completed' => 'scorm_completed',
44
        'passed' => 'scorm_completed',
45
        'succeeded' => 'scorm_completed',
46
        'browsed' => 'scorm_completed',
47
    ];
48
49
    public $attempt = 0; // The number for the current ID view.
50
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
51
    public $current; // Id of the current item the user is viewing.
52
    public $current_score; // The score of the current item.
53
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
54
    public $current_time_stop; // The time the user closed this resource.
55
    public $default_status = 'not attempted';
56
    public $encoding = 'UTF-8';
57
    public $error = '';
58
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
59
    public $index; // The index of the active learnpath_item in $ordered_items array.
60
    /** @var learnpathItem[] */
61
    public $items = [];
62
    public $last; // item_id of last item viewed in the learning path.
63
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
64
    public $license; // Which license this course has been given - not used yet on 20060522.
65
    public $lp_id; // DB iid for this learnpath.
66
    public $lp_view_id; // DB ID for lp_view
67
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
68
    public $message = '';
69
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
70
    public $name; // Learnpath name (they generally have one).
71
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
72
    public $path = ''; // Path inside the scorm directory (if scorm).
73
    public $theme; // The current theme of the learning path.
74
    public $preview_image; // The current image 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->preview_image = $entity->getPreviewImage();
163
                $this->author = $entity->getAuthor();
164
                $this->hide_toc_frame = $entity->getHideTocFrame();
165
                $this->lp_session_id = $entity->getSessionId();
166
                $this->use_max_score = $entity->getUseMaxScore();
167
                $this->subscribeUsers = $entity->getSubscribeUsers();
168
                $this->created_on = $entity->getCreatedOn()->format('Y-m-d H:i:s');
169
                $this->modified_on = $entity->getModifiedOn()->format('Y-m-d H:i:s');
170
                $this->ref = $entity->getRef();
171
                $this->categoryId = 0;
172
                if ($entity->getCategory()) {
173
                    $this->categoryId = $entity->getCategory()->getIid();
174
                }
175
176
                $this->accumulateScormTime = $entity->getAccumulateWorkTime();
177
178
                if (!empty($entity->getPublicatedOn())) {
179
                    $this->publicated_on = $entity->getPublicatedOn()->format('Y-m-d H:i:s');
180
                }
181
182
                if (!empty($entity->getExpiredOn())) {
183
                    $this->expired_on = $entity->getExpiredOn()->format('Y-m-d H:i:s');
184
                }
185
                if (2 == $this->type) {
186
                    if (1 == $entity->getForceCommit()) {
187
                        $this->force_commit = true;
188
                    }
189
                }
190
                $this->mode = $entity->getDefaultViewMod();
191
192
                // Check user ID.
193
                if (empty($user_id)) {
194
                    $this->error = 'User ID is empty';
195
                } else {
196
                    $userInfo = api_get_user_info($user_id);
197
                    if (!empty($userInfo)) {
198
                        $this->user_id = $userInfo['user_id'];
199
                    } else {
200
                        $this->error = 'User ID does not exist in database #'.$user_id;
201
                    }
202
                }
203
204
                // End of variables checking.
205
                $session_id = api_get_session_id();
206
                //  Get the session condition for learning paths of the base + session.
207
                $session = api_get_session_condition($session_id);
208
                // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
209
                $lp_table = Database::get_course_table(TABLE_LP_VIEW);
210
211
                // Selecting by view_count descending allows to get the highest view_count first.
212
                $sql = "SELECT * FROM $lp_table
213
                        WHERE
214
                            c_id = $course_id AND
215
                            lp_id = $lp_id AND
216
                            user_id = $user_id
217
                            $session
218
                        ORDER BY view_count DESC";
219
                $res = Database::query($sql);
220
221
                if (Database::num_rows($res) > 0) {
222
                    $row = Database::fetch_array($res);
223
                    $this->attempt = $row['view_count'];
224
                    $this->lp_view_id = $row['iid'];
225
                    $this->last_item_seen = $row['last_item'];
226
                    $this->progress_db = $row['progress'];
227
                    $this->lp_view_session_id = $row['session_id'];
228
                } elseif (!api_is_invitee()) {
229
                    $this->attempt = 1;
230
                    $params = [
231
                        'c_id' => $course_id,
232
                        'lp_id' => $lp_id,
233
                        'user_id' => $user_id,
234
                        'view_count' => 1,
235
                        'session_id' => $session_id,
236
                        'last_item' => 0,
237
                    ];
238
                    $this->last_item_seen = 0;
239
                    $this->lp_view_session_id = $session_id;
240
                    $this->lp_view_id = Database::insert($lp_table, $params);
241
                }
242
243
                // Initialise items.
244
                $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
245
                $sql = "SELECT * FROM $lp_item_table
246
                        WHERE c_id = $course_id AND lp_id = '".$this->lp_id."'
247
                        ORDER BY parent_item_id, display_order";
248
                $res = Database::query($sql);
249
250
                $lp_item_id_list = [];
251
                while ($row = Database::fetch_array($res)) {
252
                    $lp_item_id_list[] = $row['iid'];
253
                    switch ($this->type) {
254
                        case 3: //aicc
255
                            $oItem = new aiccItem('db', $row['iid'], $course_id);
256
                            if (is_object($oItem)) {
257
                                $my_item_id = $oItem->get_id();
258
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
259
                                $oItem->set_prevent_reinit($this->prevent_reinit);
260
                                // Don't use reference here as the next loop will make the pointed object change.
261
                                $this->items[$my_item_id] = $oItem;
262
                                $this->refs_list[$oItem->ref] = $my_item_id;
263
                            }
264
                            break;
265
                        case 2:
266
                            $oItem = new scormItem('db', $row['iid'], $course_id);
267
                            if (is_object($oItem)) {
268
                                $my_item_id = $oItem->get_id();
269
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
270
                                $oItem->set_prevent_reinit($this->prevent_reinit);
271
                                // Don't use reference here as the next loop will make the pointed object change.
272
                                $this->items[$my_item_id] = $oItem;
273
                                $this->refs_list[$oItem->ref] = $my_item_id;
274
                            }
275
                            break;
276
                        case 1:
277
                        default:
278
                            $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
279
                            if (is_object($oItem)) {
280
                                $my_item_id = $oItem->get_id();
281
                                // Moved down to when we are sure the item_view exists.
282
                                //$oItem->set_lp_view($this->lp_view_id);
283
                                $oItem->set_prevent_reinit($this->prevent_reinit);
284
                                // Don't use reference here as the next loop will make the pointed object change.
285
                                $this->items[$my_item_id] = $oItem;
286
                                $this->refs_list[$my_item_id] = $my_item_id;
287
                            }
288
                            break;
289
                    }
290
291
                    // Setting the object level with variable $this->items[$i][parent]
292
                    foreach ($this->items as $itemLPObject) {
293
                        $level = self::get_level_for_item(
294
                            $this->items,
295
                            $itemLPObject->db_id
296
                        );
297
                        $itemLPObject->level = $level;
298
                    }
299
300
                    // Setting the view in the item object.
301
                    if (is_object($this->items[$row['iid']])) {
302
                        $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
303
                        if (TOOL_HOTPOTATOES == $this->items[$row['iid']]->get_type()) {
304
                            $this->items[$row['iid']]->current_start_time = 0;
305
                            $this->items[$row['iid']]->current_stop_time = 0;
306
                        }
307
                    }
308
                }
309
310
                if (!empty($lp_item_id_list)) {
311
                    $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
312
                    if (!empty($lp_item_id_list_to_string)) {
313
                        // Get last viewing vars.
314
                        $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
315
                        // This query should only return one or zero result.
316
                        $sql = "SELECT lp_item_id, status
317
                                FROM $itemViewTable
318
                                WHERE
319
                                    c_id = $course_id AND
320
                                    lp_view_id = ".$this->get_view_id()." AND
321
                                    lp_item_id IN ('".$lp_item_id_list_to_string."')
322
                                ORDER BY view_count DESC ";
323
                        $status_list = [];
324
                        $res = Database::query($sql);
325
                        while ($row = Database:: fetch_array($res)) {
326
                            $status_list[$row['lp_item_id']] = $row['status'];
327
                        }
328
329
                        foreach ($lp_item_id_list as $item_id) {
330
                            if (isset($status_list[$item_id])) {
331
                                $status = $status_list[$item_id];
332
                                if (is_object($this->items[$item_id])) {
333
                                    $this->items[$item_id]->set_status($status);
334
                                    if (empty($status)) {
335
                                        $this->items[$item_id]->set_status(
336
                                            $this->default_status
337
                                        );
338
                                    }
339
                                }
340
                            } else {
341
                                if (!api_is_invitee()) {
342
                                    if (is_object($this->items[$item_id])) {
343
                                        $this->items[$item_id]->set_status(
344
                                            $this->default_status
345
                                        );
346
                                    }
347
348
                                    if (!empty($this->lp_view_id)) {
349
                                        // Add that row to the lp_item_view table so that
350
                                        // we have something to show in the stats page.
351
                                        $params = [
352
                                            'c_id' => $course_id,
353
                                            'lp_item_id' => $item_id,
354
                                            'lp_view_id' => $this->lp_view_id,
355
                                            'view_count' => 1,
356
                                            'status' => 'not attempted',
357
                                            'start_time' => time(),
358
                                            'total_time' => 0,
359
                                            'score' => 0,
360
                                        ];
361
                                        $insertId = Database::insert($itemViewTable, $params);
362
363
                                        $this->items[$item_id]->set_lp_view(
364
                                            $this->lp_view_id,
365
                                            $course_id
366
                                        );
367
                                    }
368
                                }
369
                            }
370
                        }
371
                    }
372
                }
373
374
                $this->ordered_items = self::get_flat_ordered_items_list(
375
                    $this->get_id(),
376
                    0,
377
                    $course_id
378
                );
379
                $this->max_ordered_items = 0;
380
                foreach ($this->ordered_items as $index => $dummy) {
381
                    if ($index > $this->max_ordered_items && !empty($dummy)) {
382
                        $this->max_ordered_items = $index;
383
                    }
384
                }
385
                // TODO: Define the current item better.
386
                $this->first();
387
                if ($debug) {
388
                    error_log('lp_view_session_id '.$this->lp_view_session_id);
389
                    error_log('End of learnpath constructor for learnpath '.$this->get_id());
390
                }
391
            } else {
392
                $this->error = 'Learnpath ID does not exist in database ('.$sql.')';
393
            }
394
        }
395
    }
396
397
    public function getEntity(): CLp
398
    {
399
        return $this->entity;
400
    }
401
402
    /**
403
     * @return string
404
     */
405
    public function getCourseCode()
406
    {
407
        return $this->cc;
408
    }
409
410
    /**
411
     * @return int
412
     */
413
    public function get_course_int_id()
414
    {
415
        return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
416
    }
417
418
    /**
419
     * @param $course_id
420
     *
421
     * @return int
422
     */
423
    public function set_course_int_id($course_id)
424
    {
425
        return $this->course_int_id = (int) $course_id;
426
    }
427
428
    /**
429
     * Function rewritten based on old_add_item() from Yannick Warnier.
430
     * Due the fact that users can decide where the item should come, I had to overlook this function and
431
     * I found it better to rewrite it. Old function is still available.
432
     * Added also the possibility to add a description.
433
     *
434
     * @param int    $parent
435
     * @param int    $previous
436
     * @param string $type
437
     * @param int    $id               resource ID (ref)
438
     * @param string $title
439
     * @param string $description
440
     * @param int    $prerequisites
441
     * @param int    $max_time_allowed
442
     * @param int    $userId
443
     *
444
     * @return int
445
     */
446
    public function add_item(
447
        $parent,
448
        $previous,
449
        $type = 'dir',
450
        $id,
451
        $title,
452
        $description,
453
        $prerequisites = 0,
454
        $max_time_allowed = 0,
455
        $userId = 0
456
    ) {
457
        $course_id = $this->course_info['real_id'];
458
        if (empty($course_id)) {
459
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
460
            $this->course_info = api_get_course_info($this->cc);
461
            $course_id = $this->course_info['real_id'];
462
        }
463
        $userId = empty($userId) ? api_get_user_id() : $userId;
464
        $sessionId = api_get_session_id();
465
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
466
        $_course = $this->course_info;
467
        $parent = (int) $parent;
468
        $previous = (int) $previous;
469
        $id = (int) $id;
470
        $max_time_allowed = (int) $max_time_allowed;
471
        if (empty($max_time_allowed)) {
472
            $max_time_allowed = 0;
473
        }
474
        $sql = "SELECT COUNT(iid) AS num
475
                FROM $tbl_lp_item
476
                WHERE
477
                    c_id = $course_id AND
478
                    lp_id = ".$this->get_id()." AND
479
                    parent_item_id = $parent ";
480
481
        $res_count = Database::query($sql);
482
        $row = Database::fetch_array($res_count);
483
        $num = $row['num'];
484
485
        $tmp_previous = 0;
486
        $display_order = 0;
487
        $next = 0;
488
        if ($num > 0) {
489
            if (empty($previous)) {
490
                $sql = "SELECT iid, next_item_id, display_order
491
                        FROM $tbl_lp_item
492
                        WHERE
493
                            c_id = $course_id AND
494
                            lp_id = ".$this->get_id()." AND
495
                            parent_item_id = $parent AND
496
                            previous_item_id = 0 OR
497
                            previous_item_id = $parent ";
498
                $result = Database::query($sql);
499
                $row = Database::fetch_array($result);
500
                if ($row) {
501
                    $next = $row['iid'];
502
                }
503
            } else {
504
                $previous = (int) $previous;
505
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
506
						FROM $tbl_lp_item
507
                        WHERE
508
                            c_id = $course_id AND
509
                            lp_id = ".$this->get_id()." AND
510
                            iid = $previous";
511
                $result = Database::query($sql);
512
                $row = Database::fetch_array($result);
513
                if ($row) {
514
                    $tmp_previous = $row['iid'];
515
                    $next = $row['next_item_id'];
516
                    $display_order = $row['display_order'];
517
                }
518
            }
519
        }
520
521
        $id = (int) $id;
522
        $typeCleaned = Database::escape_string($type);
523
        $max_score = 100;
524
        if ('quiz' === $type && $id) {
525
            $sql = 'SELECT SUM(ponderation)
526
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
527
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
528
                    ON
529
                        quiz_question.id = quiz_rel_question.question_id AND
530
                        quiz_question.c_id = quiz_rel_question.c_id
531
                    WHERE
532
                        quiz_rel_question.exercice_id = '.$id." AND
533
                        quiz_question.c_id = $course_id AND
534
                        quiz_rel_question.c_id = $course_id ";
535
            $rsQuiz = Database::query($sql);
536
            $max_score = Database::result($rsQuiz, 0, 0);
537
538
            // Disabling the exercise if we add it inside a LP
539
            $exercise = new Exercise($course_id);
540
            $exercise->read($id);
541
            $exercise->disable();
542
            $exercise->save();
543
        }
544
545
        $params = [
546
            'c_id' => $course_id,
547
            'lp_id' => $this->get_id(),
548
            'item_type' => $typeCleaned,
549
            'ref' => '',
550
            'title' => $title,
551
            'description' => $description,
552
            'path' => $id,
553
            'max_score' => $max_score,
554
            'parent_item_id' => $parent,
555
            'previous_item_id' => $previous,
556
            'next_item_id' => (int) $next,
557
            'display_order' => $display_order + 1,
558
            'prerequisite' => $prerequisites,
559
            'max_time_allowed' => $max_time_allowed,
560
            'min_score' => 0,
561
            'launch_data' => '',
562
        ];
563
564
        if (0 != $prerequisites) {
565
            $params['prerequisite'] = $prerequisites;
566
        }
567
568
        $new_item_id = Database::insert($tbl_lp_item, $params);
569
        if ($new_item_id) {
570
            if (!empty($next)) {
571
                $sql = "UPDATE $tbl_lp_item
572
                        SET previous_item_id = $new_item_id
573
                        WHERE c_id = $course_id AND iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
574
                Database::query($sql);
575
            }
576
577
            // Update the item that should be before the new item.
578
            if (!empty($tmp_previous)) {
579
                $sql = "UPDATE $tbl_lp_item
580
                        SET next_item_id = $new_item_id
581
                        WHERE c_id = $course_id AND iid = $tmp_previous";
582
                Database::query($sql);
583
            }
584
585
            // Update all the items after the new item.
586
            $sql = "UPDATE $tbl_lp_item
587
                        SET display_order = display_order + 1
588
                    WHERE
589
                        c_id = $course_id AND
590
                        lp_id = ".$this->get_id()." AND
591
                        iid <> $new_item_id AND
592
                        parent_item_id = $parent AND
593
                        display_order > $display_order";
594
            Database::query($sql);
595
596
            // Update the item that should come after the new item.
597
            $sql = "UPDATE $tbl_lp_item
598
                    SET ref = $new_item_id
599
                    WHERE c_id = $course_id AND iid = $new_item_id";
600
            Database::query($sql);
601
602
            $sql = "UPDATE $tbl_lp_item
603
                    SET previous_item_id = ".$this->getLastInFirstLevel()."
604
                    WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
605
            Database::query($sql);
606
607
            // Upload audio.
608
            if (!empty($_FILES['mp3']['name'])) {
609
                // Create the audio folder if it does not exist yet.
610
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
611
                if (!is_dir($filepath.'audio')) {
612
                    mkdir(
613
                        $filepath.'audio',
614
                        api_get_permissions_for_new_directories()
615
                    );
616
                    $audio_id = DocumentManager::addDocument(
617
                        $_course,
618
                        '/audio',
619
                        'folder',
620
                        0,
621
                        'audio',
622
                        '',
623
                        0,
624
                        true,
625
                        null,
626
                        $sessionId,
627
                        $userId
628
                    );
629
                }
630
631
                $file_path = handle_uploaded_document(
632
                    $_course,
633
                    $_FILES['mp3'],
634
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
635
                    '/audio',
636
                    $userId,
637
                    '',
638
                    '',
639
                    '',
640
                    '',
641
                    false
642
                );
643
644
                // Getting the filename only.
645
                $file_components = explode('/', $file_path);
646
                $file = $file_components[count($file_components) - 1];
647
648
                // Store the mp3 file in the lp_item table.
649
                $sql = "UPDATE $tbl_lp_item SET
650
                          audio = '".Database::escape_string($file)."'
651
                        WHERE iid = '".intval($new_item_id)."'";
652
                Database::query($sql);
653
            }
654
        }
655
656
        return $new_item_id;
657
    }
658
659
    /**
660
     * Static admin function allowing addition of a learnpath to a course.
661
     *
662
     * @param string $courseCode
663
     * @param string $name
664
     * @param string $description
665
     * @param string $learnpath
666
     * @param string $origin
667
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
668
     * @param string $publicated_on
669
     * @param string $expired_on
670
     * @param int    $categoryId
671
     * @param int    $userId
672
     *
673
     * @return int The new learnpath ID on success, 0 on failure
674
     */
675
    public static function add_lp(
676
        $courseCode,
677
        $name,
678
        $description = '',
679
        $learnpath = 'guess',
680
        $origin = 'zip',
681
        $zipname = '',
682
        $publicated_on = '',
683
        $expired_on = '',
684
        $categoryId = 0,
685
        $userId = 0
686
    ) {
687
        global $charset;
688
689
        if (!empty($courseCode)) {
690
            $courseInfo = api_get_course_info($courseCode);
691
            $course_id = $courseInfo['real_id'];
692
        } else {
693
            $course_id = api_get_course_int_id();
694
            $courseInfo = api_get_course_info();
695
        }
696
697
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
698
        // Check course code exists.
699
        // Check lp_name doesn't exist, otherwise append something.
700
        $i = 0;
701
        $categoryId = (int) $categoryId;
702
        // Session id.
703
        $session_id = api_get_session_id();
704
        $userId = empty($userId) ? api_get_user_id() : $userId;
705
706
        if (empty($publicated_on)) {
707
            $publicated_on = null;
708
        } else {
709
            $publicated_on = api_get_utc_datetime($publicated_on, true, true);
710
        }
711
712
        if (empty($expired_on)) {
713
            $expired_on = null;
714
        } else {
715
            $expired_on = api_get_utc_datetime($expired_on, true, true);
716
        }
717
718
        $check_name = "SELECT * FROM $tbl_lp
719
                       WHERE c_id = $course_id AND name = '".Database::escape_string($name)."'";
720
        $res_name = Database::query($check_name);
721
722
        while (Database::num_rows($res_name)) {
723
            // There is already one such name, update the current one a bit.
724
            $i++;
725
            $name = $name.' - '.$i;
726
            $check_name = "SELECT * FROM $tbl_lp
727
                           WHERE c_id = $course_id AND name = '".Database::escape_string($name)."' ";
728
            $res_name = Database::query($check_name);
729
        }
730
        // New name does not exist yet; keep it.
731
        // Escape description.
732
        // Kevin: added htmlentities().
733
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
734
        $type = 1;
735
        switch ($learnpath) {
736
            case 'guess':
737
            case 'aicc':
738
                break;
739
            case 'dokeos':
740
            case 'chamilo':
741
                $type = 1;
742
                break;
743
        }
744
745
        $id = null;
746
        $sessionEntity = api_get_session_entity();
747
        $courseEntity = api_get_course_entity($courseInfo['real_id']);
748
749
        switch ($origin) {
750
            case 'zip':
751
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
752
                break;
753
            case 'manual':
754
            default:
755
                $get_max = "SELECT MAX(display_order)
756
                            FROM $tbl_lp WHERE c_id = $course_id";
757
                $res_max = Database::query($get_max);
758
                if (Database::num_rows($res_max) < 1) {
759
                    $dsp = 1;
760
                } else {
761
                    $row = Database::fetch_array($res_max);
762
                    $dsp = $row[0] + 1;
763
                }
764
765
                $category = null;
766
                if (!empty($categoryId)) {
767
                    $category = Container::getLpCategoryRepository()->find($categoryId);
768
                }
769
770
                $lp = new CLp();
771
                $lp
772
                    ->setCId($course_id)
773
                    ->setLpType($type)
774
                    ->setName($name)
775
                    ->setDescription($description)
776
                    ->setDisplayOrder($dsp)
777
                    ->setSessionId($session_id)
778
                    ->setCategory($category)
779
                    ->setPublicatedOn($publicated_on)
780
                    ->setExpiredOn($expired_on)
781
                    ->setParent($courseEntity)
782
                    ->addCourseLink($courseEntity, $sessionEntity)
783
                ;
784
785
                $repo = Container::getLpRepository();
786
                $em = Database::getManager();
787
                $em->persist($lp);
788
                $em->flush();
789
790
                if ($lp->getIid()) {
791
                    $id = $lp->getIid();
792
                }
793
794
                // Insert into item_property.
795
                /*api_item_property_update(
796
                    $courseInfo,
797
                    TOOL_LEARNPATH,
798
                    $id,
799
                    'LearnpathAdded',
800
                    $userId
801
                );
802
                api_set_default_visibility(
803
                    $id,
804
                    TOOL_LEARNPATH,
805
                    0,
806
                    $courseInfo,
807
                    $session_id,
808
                    $userId
809
                );*/
810
811
                break;
812
        }
813
814
        return $id;
815
    }
816
817
    /**
818
     * Auto completes the parents of an item in case it's been completed or passed.
819
     *
820
     * @param int $item Optional ID of the item from which to look for parents
821
     */
822
    public function autocomplete_parents($item)
823
    {
824
        $debug = $this->debug;
825
826
        if (empty($item)) {
827
            $item = $this->current;
828
        }
829
830
        $currentItem = $this->getItem($item);
831
        if ($currentItem) {
832
            $parent_id = $currentItem->get_parent();
833
            $parent = $this->getItem($parent_id);
834
            if ($parent) {
835
                // if $item points to an object and there is a parent.
836
                if ($debug) {
837
                    error_log(
838
                        'Autocompleting parent of item '.$item.' '.
839
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
840
                        0
841
                    );
842
                }
843
844
                // New experiment including failed and browsed in completed status.
845
                //$current_status = $currentItem->get_status();
846
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
847
                // Fixes chapter auto complete
848
                if (true) {
849
                    // If the current item is completed or passes or succeeded.
850
                    $updateParentStatus = true;
851
                    if ($debug) {
852
                        error_log('Status of current item is alright');
853
                    }
854
855
                    foreach ($parent->get_children() as $childItemId) {
856
                        $childItem = $this->getItem($childItemId);
857
858
                        // If children was not set try to get the info
859
                        if (empty($childItem->db_item_view_id)) {
860
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
861
                        }
862
863
                        // Check all his brothers (parent's children) for completion status.
864
                        if ($childItemId != $item) {
865
                            if ($debug) {
866
                                error_log(
867
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
868
                                    0
869
                                );
870
                            }
871
                            // Trying completing parents of failed and browsed items as well.
872
                            if ($childItem->status_is(
873
                                [
874
                                    'completed',
875
                                    'passed',
876
                                    'succeeded',
877
                                    'browsed',
878
                                    'failed',
879
                                ]
880
                            )
881
                            ) {
882
                                // Keep completion status to true.
883
                                continue;
884
                            } else {
885
                                if ($debug > 2) {
886
                                    error_log(
887
                                        '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,
888
                                        0
889
                                    );
890
                                }
891
                                $updateParentStatus = false;
892
                                break;
893
                            }
894
                        }
895
                    }
896
897
                    if ($updateParentStatus) {
898
                        // If all the children were completed:
899
                        $parent->set_status('completed');
900
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
901
                        // Force the status to "completed"
902
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
903
                        $this->update_queue[$parent->get_id()] = 'completed';
904
                        if ($debug) {
905
                            error_log(
906
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
907
                                print_r($this->update_queue, 1),
908
                                0
909
                            );
910
                        }
911
                        // Recursive call.
912
                        $this->autocomplete_parents($parent->get_id());
913
                    }
914
                }
915
            } else {
916
                if ($debug) {
917
                    error_log("Parent #$parent_id does not exists");
918
                }
919
            }
920
        } else {
921
            if ($debug) {
922
                error_log("#$item is an item that doesn't have parents");
923
            }
924
        }
925
    }
926
927
    /**
928
     * Closes the current resource.
929
     *
930
     * Stops the timer
931
     * Saves into the database if required
932
     * Clears the current resource data from this object
933
     *
934
     * @return bool True on success, false on failure
935
     */
936
    public function close()
937
    {
938
        if (empty($this->lp_id)) {
939
            $this->error = 'Trying to close this learnpath but no ID is set';
940
941
            return false;
942
        }
943
        $this->current_time_stop = time();
944
        $this->ordered_items = [];
945
        $this->index = 0;
946
        unset($this->lp_id);
947
        //unset other stuff
948
        return true;
949
    }
950
951
    /**
952
     * Static admin function allowing removal of a learnpath.
953
     *
954
     * @param array  $courseInfo
955
     * @param int    $id         Learnpath ID
956
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
957
     *
958
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
959
     */
960
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
961
    {
962
        $course_id = api_get_course_int_id();
963
        if (!empty($courseInfo)) {
964
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
965
        }
966
967
        // TODO: Implement a way of getting this to work when the current object is not set.
968
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
969
        // If an ID is specifically given and the current LP is not the same, prevent delete.
970
        if (!empty($id) && ($id != $this->lp_id)) {
971
            return false;
972
        }
973
974
        $lp = Database::get_course_table(TABLE_LP_MAIN);
975
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
976
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
977
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
978
979
        // Delete lp item id.
980
        foreach ($this->items as $lpItemId => $dummy) {
981
            $sql = "DELETE FROM $lp_item_view
982
                    WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
983
            Database::query($sql);
984
        }
985
986
        // Proposed by Christophe (nickname: clefevre)
987
        $sql = "DELETE FROM $lp_item
988
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
989
        Database::query($sql);
990
991
        $sql = "DELETE FROM $lp_view
992
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
993
        Database::query($sql);
994
995
        self::toggleVisibility($this->lp_id, 0);
996
997
        if (2 == $this->type || 3 == $this->type) {
998
            // This is a scorm learning path, delete the files as well.
999
            $sql = "SELECT path FROM $lp
1000
                    WHERE iid = ".$this->lp_id;
1001
            $res = Database::query($sql);
1002
            if (Database::num_rows($res) > 0) {
1003
                $row = Database::fetch_array($res);
1004
                $path = $row['path'];
1005
                $sql = "SELECT id FROM $lp
1006
                        WHERE
1007
                            c_id = $course_id AND
1008
                            path = '$path' AND
1009
                            iid != ".$this->lp_id;
1010
                $res = Database::query($sql);
1011
                if (Database::num_rows($res) > 0) {
1012
                    // Another learning path uses this directory, so don't delete it.
1013
                    if ($this->debug > 2) {
1014
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1015
                    }
1016
                } else {
1017
                    // No other LP uses that directory, delete it.
1018
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1019
                    // The absolute system path for this course.
1020
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
1021
                    if ('remove' == $delete && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1022
                        if ($this->debug > 2) {
1023
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1024
                        }
1025
                        // Proposed by Christophe (clefevre).
1026
                        if (0 == strcmp(substr($path, -2), "/.")) {
1027
                            $path = substr($path, 0, -1); // Remove "." at the end.
1028
                        }
1029
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1030
                        rmdirr($course_scorm_dir.$path);
1031
                    }
1032
                }
1033
            }
1034
        }
1035
1036
       if (api_get_configuration_value('allow_lp_subscription_to_usergroups')) {
1037
            $table = Database::get_course_table(TABLE_LP_REL_USERGROUP);
1038
            $sql = "DELETE FROM $table
1039
                    WHERE
1040
                        lp_id = {$this->lp_id} AND
1041
                        c_id = $course_id ";
1042
            Database::query($sql);
1043
        }
1044
1045
        /*$tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1046
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1047
        // Delete tools
1048
        $sql = "DELETE FROM $tbl_tool
1049
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1050
        Database::query($sql);*/
1051
1052
        /*$sql = "DELETE FROM $lp
1053
                WHERE iid = ".$this->lp_id;
1054
        Database::query($sql);*/
1055
        $repo = Container::getLpRepository();
1056
        $lp = $repo->find($this->lp_id);
1057
        Database::getManager()->remove($lp);
1058
        Database::getManager()->flush();
1059
1060
        // Updates the display order of all lps.
1061
        $this->update_display_order();
1062
1063
        /*api_item_property_update(
1064
            api_get_course_info(),
1065
            TOOL_LEARNPATH,
1066
            $this->lp_id,
1067
            'delete',
1068
            api_get_user_id()
1069
        );*/
1070
1071
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1072
            api_get_course_id(),
1073
            4,
1074
            $id,
1075
            api_get_session_id()
1076
        );
1077
1078
        if (false !== $link_info) {
1079
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1080
        }
1081
1082
        if ('true' == api_get_setting('search_enabled')) {
1083
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1084
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1085
        }
1086
    }
1087
1088
    /**
1089
     * Removes all the children of one item - dangerous!
1090
     *
1091
     * @param int $id Element ID of which children have to be removed
1092
     *
1093
     * @return int Total number of children removed
1094
     */
1095
    public function delete_children_items($id)
1096
    {
1097
        $course_id = $this->course_info['real_id'];
1098
1099
        $num = 0;
1100
        $id = (int) $id;
1101
        if (empty($id) || empty($course_id)) {
1102
            return false;
1103
        }
1104
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1105
        $sql = "SELECT * FROM $lp_item
1106
                WHERE c_id = $course_id AND parent_item_id = $id";
1107
        $res = Database::query($sql);
1108
        while ($row = Database::fetch_array($res)) {
1109
            $num += $this->delete_children_items($row['iid']);
1110
            $sql = "DELETE FROM $lp_item
1111
                    WHERE c_id = $course_id AND iid = ".$row['iid'];
1112
            Database::query($sql);
1113
            $num++;
1114
        }
1115
1116
        return $num;
1117
    }
1118
1119
    /**
1120
     * Removes an item from the current learnpath.
1121
     *
1122
     * @param int $id Elem ID (0 if first)
1123
     *
1124
     * @return int Number of elements moved
1125
     *
1126
     * @todo implement resource removal
1127
     */
1128
    public function delete_item($id)
1129
    {
1130
        $course_id = api_get_course_int_id();
1131
        $id = (int) $id;
1132
        // TODO: Implement the resource removal.
1133
        if (empty($id) || empty($course_id)) {
1134
            return false;
1135
        }
1136
        // First select item to get previous, next, and display order.
1137
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1138
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1139
        $res_sel = Database::query($sql_sel);
1140
        if (Database::num_rows($res_sel) < 1) {
1141
            return false;
1142
        }
1143
        $row = Database::fetch_array($res_sel);
1144
        $previous = $row['previous_item_id'];
1145
        $next = $row['next_item_id'];
1146
        $display = $row['display_order'];
1147
        $parent = $row['parent_item_id'];
1148
        $lp = $row['lp_id'];
1149
        // Delete children items.
1150
        $this->delete_children_items($id);
1151
        // Now delete the item.
1152
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1153
        Database::query($sql_del);
1154
        // Now update surrounding items.
1155
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1156
                    WHERE iid = $previous";
1157
        Database::query($sql_upd);
1158
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1159
                    WHERE iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
1160
        Database::query($sql_upd);
1161
        // Now update all following items with new display order.
1162
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1163
                    WHERE
1164
                        c_id = $course_id AND
1165
                        lp_id = $lp AND
1166
                        parent_item_id = $parent AND
1167
                        display_order > $display";
1168
        Database::query($sql_all);
1169
1170
        //Removing prerequisites since the item will not longer exist
1171
        $sql_all = "UPDATE $lp_item SET prerequisite = ''
1172
                    WHERE c_id = $course_id AND prerequisite = '$id'";
1173
        Database::query($sql_all);
1174
1175
        $sql = "UPDATE $lp_item
1176
                SET previous_item_id = ".$this->getLastInFirstLevel()."
1177
                WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
1178
        Database::query($sql);
1179
1180
        // Remove from search engine if enabled.
1181
        if ('true' === api_get_setting('search_enabled')) {
1182
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1183
            $sql = 'SELECT * FROM %s
1184
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1185
                    LIMIT 1';
1186
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1187
            $res = Database::query($sql);
1188
            if (Database::num_rows($res) > 0) {
1189
                $row2 = Database::fetch_array($res);
1190
                $di = new ChamiloIndexer();
1191
                $di->remove_document($row2['search_did']);
1192
            }
1193
            $sql = 'DELETE FROM %s
1194
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1195
                    LIMIT 1';
1196
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1197
            Database::query($sql);
1198
        }
1199
    }
1200
1201
    /**
1202
     * Updates an item's content in place.
1203
     *
1204
     * @param int    $id               Element ID
1205
     * @param int    $parent           Parent item ID
1206
     * @param int    $previous         Previous item ID
1207
     * @param string $title            Item title
1208
     * @param string $description      Item description
1209
     * @param string $prerequisites    Prerequisites (optional)
1210
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1211
     * @param int    $max_time_allowed
1212
     * @param string $url
1213
     *
1214
     * @return bool True on success, false on error
1215
     */
1216
    public function edit_item(
1217
        $id,
1218
        $parent,
1219
        $previous,
1220
        $title,
1221
        $description,
1222
        $prerequisites = '0',
1223
        $audio = [],
1224
        $max_time_allowed = 0,
1225
        $url = ''
1226
    ) {
1227
        $course_id = api_get_course_int_id();
1228
        $_course = api_get_course_info();
1229
        $id = (int) $id;
1230
1231
        if (empty($max_time_allowed)) {
1232
            $max_time_allowed = 0;
1233
        }
1234
1235
        if (empty($id) || empty($_course)) {
1236
            return false;
1237
        }
1238
1239
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1240
        $sql = "SELECT * FROM $tbl_lp_item
1241
                WHERE iid = $id";
1242
        $res_select = Database::query($sql);
1243
        $row_select = Database::fetch_array($res_select);
1244
        $audio_update_sql = '';
1245
        if (is_array($audio) && !empty($audio['tmp_name']) && 0 === $audio['error']) {
1246
            // Create the audio folder if it does not exist yet.
1247
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1248
            if (!is_dir($filepath.'audio')) {
1249
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1250
                $audio_id = DocumentManager::addDocument(
1251
                    $_course,
1252
                    '/audio',
1253
                    'folder',
1254
                    0,
1255
                    'audio'
1256
                );
1257
            }
1258
1259
            // Upload file in documents.
1260
            $pi = pathinfo($audio['name']);
1261
            if ('mp3' === $pi['extension']) {
1262
                $c_det = api_get_course_info($this->cc);
1263
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1264
                $path = handle_uploaded_document(
1265
                    $c_det,
1266
                    $audio,
1267
                    $bp,
1268
                    '/audio',
1269
                    api_get_user_id(),
1270
                    0,
1271
                    null,
1272
                    0,
1273
                    'rename',
1274
                    false,
1275
                    0
1276
                );
1277
                $path = substr($path, 7);
1278
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1279
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1280
            }
1281
        }
1282
1283
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1284
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1285
1286
        // TODO: htmlspecialchars to be checked for encoding related problems.
1287
        if ($same_parent && $same_previous) {
1288
            // Only update title and description.
1289
            $sql = "UPDATE $tbl_lp_item
1290
                    SET title = '".Database::escape_string($title)."',
1291
                        prerequisite = '".$prerequisites."',
1292
                        description = '".Database::escape_string($description)."'
1293
                        ".$audio_update_sql.",
1294
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1295
                    WHERE iid = $id";
1296
            Database::query($sql);
1297
        } else {
1298
            $old_parent = $row_select['parent_item_id'];
1299
            $old_previous = $row_select['previous_item_id'];
1300
            $old_next = $row_select['next_item_id'];
1301
            $old_order = $row_select['display_order'];
1302
            $old_prerequisite = $row_select['prerequisite'];
1303
            $old_max_time_allowed = $row_select['max_time_allowed'];
1304
1305
            /* BEGIN -- virtually remove the current item id */
1306
            /* for the next and previous item it is like the current item doesn't exist anymore */
1307
            if (0 != $old_previous) {
1308
                // Next
1309
                $sql = "UPDATE $tbl_lp_item
1310
                        SET next_item_id = $old_next
1311
                        WHERE iid = $old_previous";
1312
                Database::query($sql);
1313
            }
1314
1315
            if (!empty($old_next)) {
1316
                // Previous
1317
                $sql = "UPDATE $tbl_lp_item
1318
                        SET previous_item_id = $old_previous
1319
                        WHERE iid = $old_next";
1320
                Database::query($sql);
1321
            }
1322
1323
            // display_order - 1 for every item with a display_order
1324
            // bigger then the display_order of the current item.
1325
            $sql = "UPDATE $tbl_lp_item
1326
                    SET display_order = display_order - 1
1327
                    WHERE
1328
                        c_id = $course_id AND
1329
                        display_order > $old_order AND
1330
                        lp_id = ".$this->lp_id." AND
1331
                        parent_item_id = $old_parent";
1332
            Database::query($sql);
1333
            /* END -- virtually remove the current item id */
1334
1335
            /* BEGIN -- update the current item id to his new location */
1336
            if (0 == $previous) {
1337
                // Select the data of the item that should come after the current item.
1338
                $sql = "SELECT id, display_order
1339
                        FROM $tbl_lp_item
1340
                        WHERE
1341
                            c_id = $course_id AND
1342
                            lp_id = ".$this->lp_id." AND
1343
                            parent_item_id = $parent AND
1344
                            previous_item_id = $previous";
1345
                $res_select_old = Database::query($sql);
1346
                $row_select_old = Database::fetch_array($res_select_old);
1347
1348
                // If the new parent didn't have children before.
1349
                if (0 == Database::num_rows($res_select_old)) {
1350
                    $new_next = 0;
1351
                    $new_order = 1;
1352
                } else {
1353
                    $new_next = $row_select_old['id'];
1354
                    $new_order = $row_select_old['display_order'];
1355
                }
1356
            } else {
1357
                // Select the data of the item that should come before the current item.
1358
                $sql = "SELECT next_item_id, display_order
1359
                        FROM $tbl_lp_item
1360
                        WHERE iid = $previous";
1361
                $res_select_old = Database::query($sql);
1362
                $row_select_old = Database::fetch_array($res_select_old);
1363
                $new_next = $row_select_old['next_item_id'];
1364
                $new_order = $row_select_old['display_order'] + 1;
1365
            }
1366
1367
            // TODO: htmlspecialchars to be checked for encoding related problems.
1368
            // Update the current item with the new data.
1369
            $sql = "UPDATE $tbl_lp_item
1370
                    SET
1371
                        title = '".Database::escape_string($title)."',
1372
                        description = '".Database::escape_string($description)."',
1373
                        parent_item_id = $parent,
1374
                        previous_item_id = $previous,
1375
                        next_item_id = $new_next,
1376
                        display_order = $new_order
1377
                        $audio_update_sql
1378
                    WHERE iid = $id";
1379
            Database::query($sql);
1380
1381
            if (0 != $previous) {
1382
                // Update the previous item's next_item_id.
1383
                $sql = "UPDATE $tbl_lp_item
1384
                        SET next_item_id = $id
1385
                        WHERE iid = $previous";
1386
                Database::query($sql);
1387
            }
1388
1389
            if (!empty($new_next)) {
1390
                // Update the next item's previous_item_id.
1391
                $sql = "UPDATE $tbl_lp_item
1392
                        SET previous_item_id = $id
1393
                        WHERE iid = $new_next";
1394
                Database::query($sql);
1395
            }
1396
1397
            if ($old_prerequisite != $prerequisites) {
1398
                $sql = "UPDATE $tbl_lp_item
1399
                        SET prerequisite = '$prerequisites'
1400
                        WHERE iid = $id";
1401
                Database::query($sql);
1402
            }
1403
1404
            if ($old_max_time_allowed != $max_time_allowed) {
1405
                // update max time allowed
1406
                $sql = "UPDATE $tbl_lp_item
1407
                        SET max_time_allowed = $max_time_allowed
1408
                        WHERE iid = $id";
1409
                Database::query($sql);
1410
            }
1411
1412
            // Update all the items with the same or a bigger display_order than the current item.
1413
            $sql = "UPDATE $tbl_lp_item
1414
                    SET display_order = display_order + 1
1415
                    WHERE
1416
                       c_id = $course_id AND
1417
                       lp_id = ".$this->get_id()." AND
1418
                       iid <> $id AND
1419
                       parent_item_id = $parent AND
1420
                       display_order >= $new_order";
1421
            Database::query($sql);
1422
        }
1423
1424
        if ('link' == $row_select['item_type']) {
1425
            $link = new Link();
1426
            $linkId = $row_select['path'];
1427
            $link->updateLink($linkId, $url);
1428
        }
1429
    }
1430
1431
    /**
1432
     * Updates an item's prereq in place.
1433
     *
1434
     * @param int    $id              Element ID
1435
     * @param string $prerequisite_id Prerequisite Element ID
1436
     * @param int    $minScore        Prerequisite min score
1437
     * @param int    $maxScore        Prerequisite max score
1438
     *
1439
     * @return bool True on success, false on error
1440
     */
1441
    public function edit_item_prereq(
1442
        $id,
1443
        $prerequisite_id,
1444
        $minScore = 0,
1445
        $maxScore = 100
1446
    ) {
1447
        $id = (int) $id;
1448
1449
        if (empty($id)) {
1450
            return false;
1451
        }
1452
        $prerequisite_id = (int) $prerequisite_id;
1453
1454
        if (empty($minScore) || $minScore < 0) {
1455
            $minScore = 0;
1456
        }
1457
1458
        if (empty($maxScore) || $maxScore < 0) {
1459
            $maxScore = 100;
1460
        }
1461
1462
        $minScore = (float) $minScore;
1463
        $maxScore = (float) $maxScore;
1464
1465
        if (empty($prerequisite_id)) {
1466
            $prerequisite_id = 'NULL';
1467
            $minScore = 0;
1468
            $maxScore = 100;
1469
        }
1470
1471
        $table = Database::get_course_table(TABLE_LP_ITEM);
1472
        $sql = " UPDATE $table
1473
                 SET
1474
                    prerequisite = $prerequisite_id ,
1475
                    prerequisite_min_score = $minScore ,
1476
                    prerequisite_max_score = $maxScore
1477
                 WHERE iid = $id";
1478
        Database::query($sql);
1479
1480
        return true;
1481
    }
1482
1483
    /**
1484
     * Get the specific prefix index terms of this learning path.
1485
     *
1486
     * @param string $prefix
1487
     *
1488
     * @return array Array of terms
1489
     */
1490
    public function get_common_index_terms_by_prefix($prefix)
1491
    {
1492
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1493
        $terms = get_specific_field_values_list_by_prefix(
1494
            $prefix,
1495
            $this->cc,
1496
            TOOL_LEARNPATH,
1497
            $this->lp_id
1498
        );
1499
        $prefix_terms = [];
1500
        if (!empty($terms)) {
1501
            foreach ($terms as $term) {
1502
                $prefix_terms[] = $term['value'];
1503
            }
1504
        }
1505
1506
        return $prefix_terms;
1507
    }
1508
1509
    /**
1510
     * Gets the number of items currently completed.
1511
     *
1512
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1513
     *
1514
     * @return int The number of items currently completed
1515
     */
1516
    public function get_complete_items_count($failedStatusException = false)
1517
    {
1518
        $i = 0;
1519
        $completedStatusList = [
1520
            'completed',
1521
            'passed',
1522
            'succeeded',
1523
            'browsed',
1524
        ];
1525
1526
        if (!$failedStatusException) {
1527
            $completedStatusList[] = 'failed';
1528
        }
1529
1530
        foreach ($this->items as $id => $dummy) {
1531
            // Trying failed and browsed considered "progressed" as well.
1532
            if ($this->items[$id]->status_is($completedStatusList) &&
1533
                'dir' != $this->items[$id]->get_type()
1534
            ) {
1535
                $i++;
1536
            }
1537
        }
1538
1539
        return $i;
1540
    }
1541
1542
    /**
1543
     * Gets the current item ID.
1544
     *
1545
     * @return int The current learnpath item id
1546
     */
1547
    public function get_current_item_id()
1548
    {
1549
        $current = 0;
1550
        if (!empty($this->current)) {
1551
            $current = (int) $this->current;
1552
        }
1553
1554
        return $current;
1555
    }
1556
1557
    /**
1558
     * Force to get the first learnpath item id.
1559
     *
1560
     * @return int The current learnpath item id
1561
     */
1562
    public function get_first_item_id()
1563
    {
1564
        $current = 0;
1565
        if (is_array($this->ordered_items)) {
1566
            $current = $this->ordered_items[0];
1567
        }
1568
1569
        return $current;
1570
    }
1571
1572
    /**
1573
     * Gets the total number of items available for viewing in this SCORM.
1574
     *
1575
     * @return int The total number of items
1576
     */
1577
    public function get_total_items_count()
1578
    {
1579
        return count($this->items);
1580
    }
1581
1582
    /**
1583
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1584
     *
1585
     * @return int The total no-chapters number of items
1586
     */
1587
    public function getTotalItemsCountWithoutDirs()
1588
    {
1589
        $total = 0;
1590
        $typeListNotToCount = self::getChapterTypes();
1591
        foreach ($this->items as $temp2) {
1592
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1593
                $total++;
1594
            }
1595
        }
1596
1597
        return $total;
1598
    }
1599
1600
    /**
1601
     *  Sets the first element URL.
1602
     */
1603
    public function first()
1604
    {
1605
        if ($this->debug > 0) {
1606
            error_log('In learnpath::first()', 0);
1607
            error_log('$this->last_item_seen '.$this->last_item_seen);
1608
        }
1609
1610
        // Test if the last_item_seen exists and is not a dir.
1611
        if (0 == count($this->ordered_items)) {
1612
            $this->index = 0;
1613
        }
1614
1615
        if (!empty($this->last_item_seen) &&
1616
            !empty($this->items[$this->last_item_seen]) &&
1617
            'dir' != $this->items[$this->last_item_seen]->get_type()
1618
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1619
            //&& !$this->items[$this->last_item_seen]->is_done()
1620
        ) {
1621
            if ($this->debug > 2) {
1622
                error_log(
1623
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1624
                    $this->items[$this->last_item_seen]->get_type()
1625
                );
1626
            }
1627
            $index = -1;
1628
            foreach ($this->ordered_items as $myindex => $item_id) {
1629
                if ($item_id == $this->last_item_seen) {
1630
                    $index = $myindex;
1631
                    break;
1632
                }
1633
            }
1634
            if (-1 == $index) {
1635
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1636
                if ($this->debug > 2) {
1637
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1638
                }
1639
1640
                return false;
1641
            } else {
1642
                $this->last = $this->last_item_seen;
1643
                $this->current = $this->last_item_seen;
1644
                $this->index = $index;
1645
            }
1646
        } else {
1647
            if ($this->debug > 2) {
1648
                error_log('In learnpath::first() - No last item seen', 0);
1649
            }
1650
            $index = 0;
1651
            // Loop through all ordered items and stop at the first item that is
1652
            // not a directory *and* that has not been completed yet.
1653
            while (!empty($this->ordered_items[$index]) &&
1654
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1655
                (
1656
                    'dir' == $this->items[$this->ordered_items[$index]]->get_type() ||
1657
                    true === $this->items[$this->ordered_items[$index]]->is_done()
1658
                ) && $index < $this->max_ordered_items) {
1659
                $index++;
1660
            }
1661
1662
            $this->last = $this->current;
1663
            // current is
1664
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1665
            $this->index = $index;
1666
            if ($this->debug > 2) {
1667
                error_log('$index '.$index);
1668
                error_log('In learnpath::first() - No last item seen');
1669
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1670
            }
1671
        }
1672
        if ($this->debug > 2) {
1673
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1674
        }
1675
    }
1676
1677
    /**
1678
     * Gets the js library from the database.
1679
     *
1680
     * @return string The name of the javascript library to be used
1681
     */
1682
    public function get_js_lib()
1683
    {
1684
        $lib = '';
1685
        if (!empty($this->js_lib)) {
1686
            $lib = $this->js_lib;
1687
        }
1688
1689
        return $lib;
1690
    }
1691
1692
    /**
1693
     * Gets the learnpath database ID.
1694
     *
1695
     * @return int Learnpath ID in the lp table
1696
     */
1697
    public function get_id()
1698
    {
1699
        if (!empty($this->lp_id)) {
1700
            return (int) $this->lp_id;
1701
        }
1702
1703
        return 0;
1704
    }
1705
1706
    /**
1707
     * Gets the last element URL.
1708
     *
1709
     * @return string URL to load into the viewer
1710
     */
1711
    public function get_last()
1712
    {
1713
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1714
        if (count($this->ordered_items) > 0) {
1715
            $this->index = count($this->ordered_items) - 1;
1716
1717
            return $this->ordered_items[$this->index];
1718
        }
1719
1720
        return false;
1721
    }
1722
1723
    /**
1724
     * Get the last element in the first level.
1725
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1726
     *
1727
     * @return mixed
1728
     */
1729
    public function getLastInFirstLevel()
1730
    {
1731
        try {
1732
            $lastId = Database::getManager()
1733
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1734
                WHERE i.lpId = :lp AND i.parentItemId = 0 AND i.itemType != :type ORDER BY i.displayOrder DESC')
1735
                ->setMaxResults(1)
1736
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1737
                ->getSingleScalarResult();
1738
1739
            return $lastId;
1740
        } catch (Exception $exception) {
1741
            return 0;
1742
        }
1743
    }
1744
1745
    /**
1746
     * Get the learning path name by id.
1747
     *
1748
     * @param int $lpId
1749
     *
1750
     * @return mixed
1751
     */
1752
    public static function getLpNameById($lpId)
1753
    {
1754
        $em = Database::getManager();
1755
1756
        return $em->createQuery('SELECT clp.name FROM ChamiloCourseBundle:CLp clp
1757
            WHERE clp.iid = :iid')
1758
            ->setParameter('iid', $lpId)
1759
            ->getSingleScalarResult();
1760
    }
1761
1762
    /**
1763
     * Gets the navigation bar for the learnpath display screen.
1764
     *
1765
     * @param string $barId
1766
     *
1767
     * @return string The HTML string to use as a navigation bar
1768
     */
1769
    public function get_navigation_bar($barId = '')
1770
    {
1771
        if (empty($barId)) {
1772
            $barId = 'control-top';
1773
        }
1774
        $lpId = $this->lp_id;
1775
        $mycurrentitemid = $this->get_current_item_id();
1776
1777
        $reportingText = get_lang('Reporting');
1778
        $previousText = get_lang('Previous');
1779
        $nextText = get_lang('Next');
1780
        $fullScreenText = get_lang('Back to normal screen');
1781
1782
        $settings = api_get_configuration_value('lp_view_settings');
1783
        $display = isset($settings['display']) ? $settings['display'] : false;
1784
        $reportingIcon = '
1785
            <a class="icon-toolbar"
1786
                id="stats_link"
1787
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
1788
                onclick="window.parent.API.save_asset(); return true;"
1789
                target="content_name" title="'.$reportingText.'">
1790
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1791
            </a>';
1792
1793
        if (!empty($display)) {
1794
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1795
            if (false === $showReporting) {
1796
                $reportingIcon = '';
1797
            }
1798
        }
1799
1800
        $hideArrows = false;
1801
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1802
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1803
        }
1804
1805
        $previousIcon = '';
1806
        $nextIcon = '';
1807
        if (false === $hideArrows) {
1808
            $previousIcon = '
1809
                <a class="icon-toolbar" id="scorm-previous" href="#"
1810
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1811
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1812
                </a>';
1813
1814
            $nextIcon = '
1815
                <a class="icon-toolbar" id="scorm-next" href="#"
1816
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1817
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1818
                </a>';
1819
        }
1820
1821
        if ('fullscreen' === $this->mode) {
1822
            $navbar = '
1823
                  <span id="'.$barId.'" class="buttons">
1824
                    '.$reportingIcon.'
1825
                    '.$previousIcon.'
1826
                    '.$nextIcon.'
1827
                    <a class="icon-toolbar" id="view-embedded"
1828
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1829
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1830
                    </a>
1831
                  </span>';
1832
        } else {
1833
            $navbar = '
1834
                 <span id="'.$barId.'" class="buttons text-right">
1835
                    '.$reportingIcon.'
1836
                    '.$previousIcon.'
1837
                    '.$nextIcon.'
1838
                </span>';
1839
        }
1840
1841
        return $navbar;
1842
    }
1843
1844
    /**
1845
     * Gets the next resource in queue (url).
1846
     *
1847
     * @return string URL to load into the viewer
1848
     */
1849
    public function get_next_index()
1850
    {
1851
        // TODO
1852
        $index = $this->index;
1853
        $index++;
1854
        while (
1855
            !empty($this->ordered_items[$index]) && ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) &&
1856
            $index < $this->max_ordered_items
1857
        ) {
1858
            $index++;
1859
            if ($index == $this->max_ordered_items) {
1860
                if ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) {
1861
                    return $this->index;
1862
                }
1863
1864
                return $index;
1865
            }
1866
        }
1867
        if (empty($this->ordered_items[$index])) {
1868
            return $this->index;
1869
        }
1870
1871
        return $index;
1872
    }
1873
1874
    /**
1875
     * Gets item_id for the next element.
1876
     *
1877
     * @return int Next item (DB) ID
1878
     */
1879
    public function get_next_item_id()
1880
    {
1881
        $new_index = $this->get_next_index();
1882
        if (!empty($new_index)) {
1883
            if (isset($this->ordered_items[$new_index])) {
1884
                return $this->ordered_items[$new_index];
1885
            }
1886
        }
1887
1888
        return 0;
1889
    }
1890
1891
    /**
1892
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
1893
     *
1894
     * Generally, the package provided is in the form of a zip file, so the function
1895
     * has been written to test a zip file. If not a zip, the function will return the
1896
     * default return value: ''
1897
     *
1898
     * @param string $file_path the path to the file
1899
     * @param string $file_name the original name of the file
1900
     *
1901
     * @return string 'scorm','aicc','scorm2004','dokeos', 'error-empty-package' if the package is empty, or '' if the package cannot be recognized
1902
     */
1903
    public static function getPackageType($file_path, $file_name)
1904
    {
1905
        // Get name of the zip file without the extension.
1906
        $file_info = pathinfo($file_name);
1907
        $extension = $file_info['extension']; // Extension only.
1908
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
1909
                'dll',
1910
                'exe',
1911
            ])) {
1912
            return 'oogie';
1913
        }
1914
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
1915
                'dll',
1916
                'exe',
1917
            ])) {
1918
            return 'woogie';
1919
        }
1920
1921
        $zipFile = new PclZip($file_path);
1922
        // Check the zip content (real size and file extension).
1923
        $zipContentArray = $zipFile->listContent();
1924
        $package_type = '';
1925
        $manifest = '';
1926
        $aicc_match_crs = 0;
1927
        $aicc_match_au = 0;
1928
        $aicc_match_des = 0;
1929
        $aicc_match_cst = 0;
1930
        $countItems = 0;
1931
1932
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
1933
        if (is_array($zipContentArray)) {
1934
            $countItems = count($zipContentArray);
1935
            if ($countItems > 0) {
1936
                foreach ($zipContentArray as $thisContent) {
1937
                    if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
1938
                        // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
1939
                    } elseif (false !== stristr($thisContent['filename'], 'imsmanifest.xml')) {
1940
                        $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
1941
                        $package_type = 'scorm';
1942
                        break; // Exit the foreach loop.
1943
                    } elseif (
1944
                        preg_match('/aicc\//i', $thisContent['filename']) ||
1945
                        in_array(
1946
                            strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
1947
                            ['crs', 'au', 'des', 'cst']
1948
                        )
1949
                    ) {
1950
                        $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
1951
                        switch ($ext) {
1952
                            case 'crs':
1953
                                $aicc_match_crs = 1;
1954
                                break;
1955
                            case 'au':
1956
                                $aicc_match_au = 1;
1957
                                break;
1958
                            case 'des':
1959
                                $aicc_match_des = 1;
1960
                                break;
1961
                            case 'cst':
1962
                                $aicc_match_cst = 1;
1963
                                break;
1964
                            default:
1965
                                break;
1966
                        }
1967
                        //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
1968
                    } else {
1969
                        $package_type = '';
1970
                    }
1971
                }
1972
            }
1973
        }
1974
1975
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
1976
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
1977
            $package_type = 'aicc';
1978
        }
1979
1980
        // Try with chamilo course builder
1981
        if (empty($package_type)) {
1982
            // Sometimes users will try to upload an empty zip, or a zip with
1983
            // only a folder. Catch that and make the calling function aware.
1984
            // If the single file was the imsmanifest.xml, then $package_type
1985
            // would be 'scorm' and we wouldn't be here.
1986
            if ($countItems < 2) {
1987
                return 'error-empty-package';
1988
            }
1989
            $package_type = 'chamilo';
1990
        }
1991
1992
        return $package_type;
1993
    }
1994
1995
    /**
1996
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
1997
     *
1998
     * @return string URL to load into the viewer
1999
     */
2000
    public function get_previous_index()
2001
    {
2002
        $index = $this->index;
2003
        if (isset($this->ordered_items[$index - 1])) {
2004
            $index--;
2005
            while (isset($this->ordered_items[$index]) &&
2006
                ('dir' == $this->items[$this->ordered_items[$index]]->get_type())
2007
            ) {
2008
                $index--;
2009
                if ($index < 0) {
2010
                    return $this->index;
2011
                }
2012
            }
2013
        }
2014
2015
        return $index;
2016
    }
2017
2018
    /**
2019
     * Gets item_id for the next element.
2020
     *
2021
     * @return int Previous item (DB) ID
2022
     */
2023
    public function get_previous_item_id()
2024
    {
2025
        $index = $this->get_previous_index();
2026
2027
        return $this->ordered_items[$index];
2028
    }
2029
2030
    /**
2031
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2032
     *
2033
     * @param int    $lpItemId
2034
     * @param string $autostart
2035
     *
2036
     * @return string The mediaplayer HTML
2037
     */
2038
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2039
    {
2040
        $course_id = api_get_course_int_id();
2041
        $courseInfo = api_get_course_info();
2042
        $lpItemId = (int) $lpItemId;
2043
2044
        if (empty($courseInfo) || empty($lpItemId)) {
2045
            return '';
2046
        }
2047
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
2048
2049
        if (empty($item)) {
2050
            return '';
2051
        }
2052
2053
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2054
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2055
        $itemViewId = (int) $item->db_item_view_id;
2056
2057
        // Getting all the information about the item.
2058
        $sql = "SELECT lp_view.status
2059
                FROM $tbl_lp_item as lpi
2060
                INNER JOIN $tbl_lp_item_view as lp_view
2061
                ON (lpi.iid = lp_view.lp_item_id)
2062
                WHERE
2063
                    lp_view.iid = $itemViewId AND
2064
                    lpi.iid = $lpItemId AND
2065
                    lp_view.c_id = $course_id";
2066
        $result = Database::query($sql);
2067
        $row = Database::fetch_assoc($result);
2068
        $output = '';
2069
        $audio = $item->audio;
2070
2071
        if (!empty($audio)) {
2072
            $list = $_SESSION['oLP']->get_toc();
2073
2074
            switch ($item->get_type()) {
2075
                case 'quiz':
2076
                    $type_quiz = false;
2077
                    foreach ($list as $toc) {
2078
                        if ($toc['id'] == $_SESSION['oLP']->current) {
2079
                            $type_quiz = true;
2080
                        }
2081
                    }
2082
2083
                    if ($type_quiz) {
2084
                        if (1 == $_SESSION['oLP']->prevent_reinit) {
2085
                            $autostart_audio = 'completed' === $row['status'] ? 'false' : 'true';
2086
                        } else {
2087
                            $autostart_audio = $autostart;
2088
                        }
2089
                    }
2090
                    break;
2091
                case TOOL_READOUT_TEXT:
2092
                    $autostart_audio = 'false';
2093
                    break;
2094
                default:
2095
                    $autostart_audio = 'true';
2096
            }
2097
2098
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'.$audio;
2099
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document'.$audio.'?'.api_get_cidreq();
2100
2101
            $player = Display::getMediaPlayer(
2102
                $file,
2103
                [
2104
                    'id' => 'lp_audio_media_player',
2105
                    'url' => $url,
2106
                    'autoplay' => $autostart_audio,
2107
                    'width' => '100%',
2108
                ]
2109
            );
2110
2111
            // The mp3 player.
2112
            $output = '<div id="container">';
2113
            $output .= $player;
2114
            $output .= '</div>';
2115
        }
2116
2117
        return $output;
2118
    }
2119
2120
    /**
2121
     * @param int   $studentId
2122
     * @param int   $prerequisite
2123
     * @param array $courseInfo
2124
     * @param int   $sessionId
2125
     *
2126
     * @return bool
2127
     */
2128
    public static function isBlockedByPrerequisite(
2129
        $studentId,
2130
        $prerequisite,
2131
        $courseInfo,
2132
        $sessionId
2133
    ) {
2134
        if (empty($courseInfo)) {
2135
            return false;
2136
        }
2137
2138
        $courseId = $courseInfo['real_id'];
2139
2140
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2141
        if ($allow) {
2142
            if (api_is_allowed_to_edit() ||
2143
                api_is_platform_admin(true) ||
2144
                api_is_drh() ||
2145
                api_is_coach($sessionId, $courseId, false)
2146
            ) {
2147
                return false;
2148
            }
2149
        }
2150
2151
        $isBlocked = false;
2152
        if (!empty($prerequisite)) {
2153
            $progress = self::getProgress(
2154
                $prerequisite,
2155
                $studentId,
2156
                $courseId,
2157
                $sessionId
2158
            );
2159
            if ($progress < 100) {
2160
                $isBlocked = true;
2161
            }
2162
2163
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2164
                // Block if it does not exceed minimum time
2165
                // Minimum time (in minutes) to pass the learning path
2166
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
2167
2168
                if ($accumulateWorkTime > 0) {
2169
                    // Total time in course (sum of times in learning paths from course)
2170
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
2171
2172
                    // Connect with the plugin_licences_course_session table
2173
                    // which indicates what percentage of the time applies
2174
                    // Minimum connection percentage
2175
                    $perc = 100;
2176
                    // Time from the course
2177
                    $tc = $accumulateWorkTimeTotal;
2178
2179
                    // Percentage of the learning paths
2180
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
2181
                    // Minimum time for each learning path
2182
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
2183
2184
                    // Spent time (in seconds) so far in the learning path
2185
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
2186
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
2187
2188
                    if ($lpTime < ($accumulateWorkTime * 60)) {
2189
                        $isBlocked = true;
2190
                    }
2191
                }
2192
            }
2193
        }
2194
2195
        return $isBlocked;
2196
    }
2197
2198
    /**
2199
     * Checks if the learning path is visible for student after the progress
2200
     * of its prerequisite is completed, considering the time availability and
2201
     * the LP visibility.
2202
     *
2203
     * @param int   $student_id
2204
     * @param array $courseInfo
2205
     * @param int   $sessionId
2206
     *
2207
     * @return bool
2208
     */
2209
    public static function is_lp_visible_for_student(
2210
        CLp $lp,
2211
        $student_id,
2212
        $courseInfo = [],
2213
        $sessionId = 0
2214
    ) {
2215
        $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
2216
        $sessionId = (int) $sessionId;
2217
2218
        if (empty($courseInfo)) {
2219
            return false;
2220
        }
2221
2222
        if (empty($sessionId)) {
2223
            $sessionId = api_get_session_id();
2224
        }
2225
2226
        $courseId = $courseInfo['real_id'];
2227
2228
        /*$itemInfo = api_get_item_property_info(
2229
            $courseId,
2230
            TOOL_LEARNPATH,
2231
            $lp_id,
2232
            $sessionId
2233
        );*/
2234
2235
        $visibility = $lp->isVisible($courseInfo['entity'], api_get_session_entity($sessionId));
2236
        // If the item was deleted.
2237
        if (false === $visibility) {
2238
            return false;
2239
        }
2240
2241
        $lp_id = $lp->getIid();
2242
        // @todo remove this query and load the row info as a parameter
2243
        $table = Database::get_course_table(TABLE_LP_MAIN);
2244
        // Get current prerequisite
2245
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on, category_id
2246
                FROM $table
2247
                WHERE iid = $lp_id";
2248
        $rs = Database::query($sql);
2249
        $now = time();
2250
        if (Database::num_rows($rs) > 0) {
2251
            $row = Database::fetch_array($rs, 'ASSOC');
2252
2253
            if (!empty($row['category_id'])) {
2254
                $em = Database::getManager();
2255
                $category = $em->getRepository('ChamiloCourseBundle:CLpCategory')->find($row['category_id']);
2256
                if (false === self::categoryIsVisibleForStudent($category, api_get_user_entity($student_id))) {
2257
                    return false;
2258
                }
2259
            }
2260
2261
            $prerequisite = $row['prerequisite'];
2262
            $is_visible = true;
2263
2264
            $isBlocked = self::isBlockedByPrerequisite(
2265
                $student_id,
2266
                $prerequisite,
2267
                $courseInfo,
2268
                $sessionId
2269
            );
2270
2271
            if ($isBlocked) {
2272
                $is_visible = false;
2273
            }
2274
2275
            // Also check the time availability of the LP
2276
            if ($is_visible) {
2277
                // Adding visibility restrictions
2278
                if (!empty($row['publicated_on'])) {
2279
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2280
                        $is_visible = false;
2281
                    }
2282
                }
2283
                // Blocking empty start times see BT#2800
2284
                global $_custom;
2285
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2286
                    $_custom['lps_hidden_when_no_start_date']
2287
                ) {
2288
                    if (empty($row['publicated_on'])) {
2289
                        $is_visible = false;
2290
                    }
2291
                }
2292
2293
                if (!empty($row['expired_on'])) {
2294
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2295
                        $is_visible = false;
2296
                    }
2297
                }
2298
            }
2299
2300
            if ($is_visible) {
2301
                $subscriptionSettings = self::getSubscriptionSettings();
2302
2303
                // Check if the subscription users/group to a LP is ON
2304
                if (isset($row['subscribe_users']) && 1 == $row['subscribe_users'] &&
2305
                    true === $subscriptionSettings['allow_add_users_to_lp']
2306
                ) {
2307
                    // Try group
2308
                    $is_visible = false;
2309
                    // Checking only the user visibility
2310
                    $userVisibility = api_get_item_visibility(
2311
                        $courseInfo,
2312
                        'learnpath',
2313
                        $row['id'],
2314
                        $sessionId,
2315
                        $student_id,
2316
                        'LearnpathSubscription'
2317
                    );
2318
2319
                    if (1 == $userVisibility) {
2320
                        $is_visible = true;
2321
                    } else {
2322
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
2323
                        if (!empty($userGroups)) {
2324
                            foreach ($userGroups as $groupInfo) {
2325
                                $groupId = $groupInfo['iid'];
2326
                                $userVisibility = api_get_item_visibility(
2327
                                    $courseInfo,
2328
                                    'learnpath',
2329
                                    $row['id'],
2330
                                    $sessionId,
2331
                                    null,
2332
                                    'LearnpathSubscription',
2333
                                    $groupId
2334
                                );
2335
2336
                                if (1 == $userVisibility) {
2337
                                    $is_visible = true;
2338
                                    break;
2339
                                }
2340
                            }
2341
                        }
2342
                    }
2343
                }
2344
            }
2345
2346
            return $is_visible;
2347
        }
2348
2349
        return false;
2350
    }
2351
2352
    /**
2353
     * @param int $lpId
2354
     * @param int $userId
2355
     * @param int $courseId
2356
     * @param int $sessionId
2357
     *
2358
     * @return int
2359
     */
2360
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2361
    {
2362
        $lpId = (int) $lpId;
2363
        $userId = (int) $userId;
2364
        $courseId = (int) $courseId;
2365
        $sessionId = (int) $sessionId;
2366
2367
        $sessionCondition = api_get_session_condition($sessionId);
2368
        $table = Database::get_course_table(TABLE_LP_VIEW);
2369
        $sql = "SELECT progress FROM $table
2370
                WHERE
2371
                    c_id = $courseId AND
2372
                    lp_id = $lpId AND
2373
                    user_id = $userId $sessionCondition ";
2374
        $res = Database::query($sql);
2375
2376
        $progress = 0;
2377
        if (Database::num_rows($res) > 0) {
2378
            $row = Database::fetch_array($res);
2379
            $progress = (int) $row['progress'];
2380
        }
2381
2382
        return $progress;
2383
    }
2384
2385
    /**
2386
     * @param array $lpList
2387
     * @param int   $userId
2388
     * @param int   $courseId
2389
     * @param int   $sessionId
2390
     *
2391
     * @return array
2392
     */
2393
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2394
    {
2395
        $lpList = array_map('intval', $lpList);
2396
        if (empty($lpList)) {
2397
            return [];
2398
        }
2399
2400
        $lpList = implode("','", $lpList);
2401
2402
        $userId = (int) $userId;
2403
        $courseId = (int) $courseId;
2404
        $sessionId = (int) $sessionId;
2405
2406
        $sessionCondition = api_get_session_condition($sessionId);
2407
        $table = Database::get_course_table(TABLE_LP_VIEW);
2408
        $sql = "SELECT lp_id, progress FROM $table
2409
                WHERE
2410
                    c_id = $courseId AND
2411
                    lp_id IN ('".$lpList."') AND
2412
                    user_id = $userId $sessionCondition ";
2413
        $res = Database::query($sql);
2414
2415
        if (Database::num_rows($res) > 0) {
2416
            $list = [];
2417
            while ($row = Database::fetch_array($res)) {
2418
                $list[$row['lp_id']] = $row['progress'];
2419
            }
2420
2421
            return $list;
2422
        }
2423
2424
        return [];
2425
    }
2426
2427
    /**
2428
     * Displays a progress bar
2429
     * completed so far.
2430
     *
2431
     * @param int    $percentage Progress value to display
2432
     * @param string $text_add   Text to display near the progress value
2433
     *
2434
     * @return string HTML string containing the progress bar
2435
     */
2436
    public static function get_progress_bar($percentage = -1, $text_add = '')
2437
    {
2438
        $text = $percentage.$text_add;
2439
        $output = '<div class="progress">
2440
            <div id="progress_bar_value"
2441
                class="progress-bar progress-bar-success" role="progressbar"
2442
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2443
            '.$text.'
2444
            </div>
2445
        </div>';
2446
2447
        return $output;
2448
    }
2449
2450
    /**
2451
     * @param string $mode can be '%' or 'abs'
2452
     *                     otherwise this value will be used $this->progress_bar_mode
2453
     *
2454
     * @return string
2455
     */
2456
    public function getProgressBar($mode = null)
2457
    {
2458
        list($percentage, $text_add) = $this->get_progress_bar_text($mode);
2459
2460
        return self::get_progress_bar($percentage, $text_add);
2461
    }
2462
2463
    /**
2464
     * Gets the progress bar info to display inside the progress bar.
2465
     * Also used by scorm_api.php.
2466
     *
2467
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2468
     *                     we display a number of completed elements per total elements
2469
     * @param int    $add  Additional steps to fake as completed
2470
     *
2471
     * @return array Percentage or number and symbol (% or /xx)
2472
     */
2473
    public function get_progress_bar_text($mode = '', $add = 0)
2474
    {
2475
        if (empty($mode)) {
2476
            $mode = $this->progress_bar_mode;
2477
        }
2478
        $text = '';
2479
        $percentage = 0;
2480
        // If the option to use the score as progress is set for this learning
2481
        // path, then the rules are completely different: we assume only one
2482
        // item exists and the progress of the LP depends on the score
2483
        $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
2484
        if (true === $scoreAsProgressSetting) {
2485
            $scoreAsProgress = $this->getUseScoreAsProgress();
2486
            if ($scoreAsProgress) {
2487
                // Get single item's score
2488
                $itemId = $this->get_current_item_id();
2489
                $item = $this->getItem($itemId);
2490
                $score = $item->get_score();
2491
                $maxScore = $item->get_max();
2492
                if ($mode = '%') {
2493
                    if (!empty($maxScore)) {
2494
                        $percentage = ((float) $score / (float) $maxScore) * 100;
2495
                    }
2496
                    $percentage = number_format($percentage, 0);
2497
                    $text = '%';
2498
                } else {
2499
                    $percentage = $score;
2500
                    $text = '/'.$maxScore;
2501
                }
2502
2503
                return [$percentage, $text];
2504
            }
2505
        }
2506
        // otherwise just continue the normal processing of progress
2507
        $total_items = $this->getTotalItemsCountWithoutDirs();
2508
        $completeItems = $this->get_complete_items_count();
2509
        if (0 != $add) {
2510
            $completeItems += $add;
2511
        }
2512
        if ($completeItems > $total_items) {
2513
            $completeItems = $total_items;
2514
        }
2515
        if ('%' == $mode) {
2516
            if ($total_items > 0) {
2517
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2518
            }
2519
            $percentage = number_format($percentage, 0);
2520
            $text = '%';
2521
        } elseif ('abs' === $mode) {
2522
            $percentage = $completeItems;
2523
            $text = '/'.$total_items;
2524
        }
2525
2526
        return [
2527
            $percentage,
2528
            $text,
2529
        ];
2530
    }
2531
2532
    /**
2533
     * Gets the progress bar mode.
2534
     *
2535
     * @return string The progress bar mode attribute
2536
     */
2537
    public function get_progress_bar_mode()
2538
    {
2539
        if (!empty($this->progress_bar_mode)) {
2540
            return $this->progress_bar_mode;
2541
        }
2542
2543
        return '%';
2544
    }
2545
2546
    /**
2547
     * Gets the learnpath theme (remote or local).
2548
     *
2549
     * @return string Learnpath theme
2550
     */
2551
    public function get_theme()
2552
    {
2553
        if (!empty($this->theme)) {
2554
            return $this->theme;
2555
        }
2556
2557
        return '';
2558
    }
2559
2560
    /**
2561
     * Gets the learnpath session id.
2562
     *
2563
     * @return int
2564
     */
2565
    public function get_lp_session_id()
2566
    {
2567
        if (!empty($this->lp_session_id)) {
2568
            return (int) $this->lp_session_id;
2569
        }
2570
2571
        return 0;
2572
    }
2573
2574
    /**
2575
     * Gets the learnpath image.
2576
     *
2577
     * @return string Web URL of the LP image
2578
     */
2579
    public function get_preview_image()
2580
    {
2581
        if (!empty($this->preview_image)) {
2582
            return $this->preview_image;
2583
        }
2584
2585
        return '';
2586
    }
2587
2588
    /**
2589
     * @param string $size
2590
     * @param string $path_type
2591
     *
2592
     * @return bool|string
2593
     */
2594
    public function get_preview_image_path($size = null, $path_type = 'web')
2595
    {
2596
        $preview_image = $this->get_preview_image();
2597
        if (isset($preview_image) && !empty($preview_image)) {
2598
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2599
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2600
2601
            if (isset($size)) {
2602
                $info = pathinfo($preview_image);
2603
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2604
2605
                if (file_exists($image_sys_path.$image_custom_size)) {
2606
                    if ('web' == $path_type) {
2607
                        return $image_path.$image_custom_size;
2608
                    } else {
2609
                        return $image_sys_path.$image_custom_size;
2610
                    }
2611
                }
2612
            } else {
2613
                if ('web' == $path_type) {
2614
                    return $image_path.$preview_image;
2615
                } else {
2616
                    return $image_sys_path.$preview_image;
2617
                }
2618
            }
2619
        }
2620
2621
        return false;
2622
    }
2623
2624
    /**
2625
     * Gets the learnpath author.
2626
     *
2627
     * @return string LP's author
2628
     */
2629
    public function get_author()
2630
    {
2631
        if (!empty($this->author)) {
2632
            return $this->author;
2633
        }
2634
2635
        return '';
2636
    }
2637
2638
    /**
2639
     * Gets hide table of contents.
2640
     *
2641
     * @return int
2642
     */
2643
    public function getHideTableOfContents()
2644
    {
2645
        return (int) $this->hide_toc_frame;
2646
    }
2647
2648
    /**
2649
     * Generate a new prerequisites string for a given item. If this item was a sco and
2650
     * its prerequisites were strings (instead of IDs), then transform those strings into
2651
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2652
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2653
     * same rule as the scormExport() method.
2654
     *
2655
     * @param int $item_id Item ID
2656
     *
2657
     * @return string Prerequisites string ready for the export as SCORM
2658
     */
2659
    public function get_scorm_prereq_string($item_id)
2660
    {
2661
        if ($this->debug > 0) {
2662
            error_log('In learnpath::get_scorm_prereq_string()');
2663
        }
2664
        if (!is_object($this->items[$item_id])) {
2665
            return false;
2666
        }
2667
        /** @var learnpathItem $oItem */
2668
        $oItem = $this->items[$item_id];
2669
        $prereq = $oItem->get_prereq_string();
2670
2671
        if (empty($prereq)) {
2672
            return '';
2673
        }
2674
        if (preg_match('/^\d+$/', $prereq) &&
2675
            isset($this->items[$prereq]) &&
2676
            is_object($this->items[$prereq])
2677
        ) {
2678
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2679
            // then simply return it (with the ITEM_ prefix).
2680
            //return 'ITEM_' . $prereq;
2681
            return $this->items[$prereq]->ref;
2682
        } else {
2683
            if (isset($this->refs_list[$prereq])) {
2684
                // It's a simple string item from which the ID can be found in the refs list,
2685
                // so we can transform it directly to an ID for export.
2686
                return $this->items[$this->refs_list[$prereq]]->ref;
2687
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2688
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2689
            } else {
2690
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2691
                // and replace them, one by one, by the internal IDs (chamilo db)
2692
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2693
                // by a space as well.
2694
                $find = [
2695
                    '&',
2696
                    '|',
2697
                    '~',
2698
                    '=',
2699
                    '<>',
2700
                    '{',
2701
                    '}',
2702
                    '*',
2703
                    '(',
2704
                    ')',
2705
                ];
2706
                $replace = [
2707
                    ' ',
2708
                    ' ',
2709
                    ' ',
2710
                    ' ',
2711
                    ' ',
2712
                    ' ',
2713
                    ' ',
2714
                    ' ',
2715
                    ' ',
2716
                    ' ',
2717
                ];
2718
                $prereq_mod = str_replace($find, $replace, $prereq);
2719
                $ids = explode(' ', $prereq_mod);
2720
                foreach ($ids as $id) {
2721
                    $id = trim($id);
2722
                    if (isset($this->refs_list[$id])) {
2723
                        $prereq = preg_replace(
2724
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2725
                            'ITEM_'.$this->refs_list[$id],
2726
                            $prereq
2727
                        );
2728
                    }
2729
                }
2730
2731
                return $prereq;
2732
            }
2733
        }
2734
    }
2735
2736
    /**
2737
     * Returns the XML DOM document's node.
2738
     *
2739
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2740
     * @param string   $id       The identifier to look for
2741
     *
2742
     * @return mixed The reference to the element found with that identifier. False if not found
2743
     */
2744
    public function get_scorm_xml_node(&$children, $id)
2745
    {
2746
        for ($i = 0; $i < $children->length; $i++) {
2747
            $item_temp = $children->item($i);
2748
            if ('item' == $item_temp->nodeName) {
2749
                if ($item_temp->getAttribute('identifier') == $id) {
2750
                    return $item_temp;
2751
                }
2752
            }
2753
            $subchildren = $item_temp->childNodes;
2754
            if ($subchildren && $subchildren->length > 0) {
2755
                $val = $this->get_scorm_xml_node($subchildren, $id);
2756
                if (is_object($val)) {
2757
                    return $val;
2758
                }
2759
            }
2760
        }
2761
2762
        return false;
2763
    }
2764
2765
    /**
2766
     * Gets the status list for all LP's items.
2767
     *
2768
     * @return array Array of [index] => [item ID => current status]
2769
     */
2770
    public function get_items_status_list()
2771
    {
2772
        $list = [];
2773
        foreach ($this->ordered_items as $item_id) {
2774
            $list[] = [
2775
                $item_id => $this->items[$item_id]->get_status(),
2776
            ];
2777
        }
2778
2779
        return $list;
2780
    }
2781
2782
    /**
2783
     * Return the number of interactions for the given learnpath Item View ID.
2784
     * This method can be used as static.
2785
     *
2786
     * @param int $lp_iv_id  Item View ID
2787
     * @param int $course_id course id
2788
     *
2789
     * @return int
2790
     */
2791
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2792
    {
2793
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2794
        $lp_iv_id = (int) $lp_iv_id;
2795
        $course_id = (int) $course_id;
2796
2797
        $sql = "SELECT count(*) FROM $table
2798
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2799
        $res = Database::query($sql);
2800
        $num = 0;
2801
        if (Database::num_rows($res)) {
2802
            $row = Database::fetch_array($res);
2803
            $num = $row[0];
2804
        }
2805
2806
        return $num;
2807
    }
2808
2809
    /**
2810
     * Return the interactions as an array for the given lp_iv_id.
2811
     * This method can be used as static.
2812
     *
2813
     * @param int $lp_iv_id Learnpath Item View ID
2814
     *
2815
     * @return array
2816
     *
2817
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2818
     */
2819
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2820
    {
2821
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2822
        $list = [];
2823
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2824
        $lp_iv_id = (int) $lp_iv_id;
2825
2826
        if (empty($lp_iv_id) || empty($course_id)) {
2827
            return [];
2828
        }
2829
2830
        $sql = "SELECT * FROM $table
2831
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2832
                ORDER BY order_id ASC";
2833
        $res = Database::query($sql);
2834
        $num = Database::num_rows($res);
2835
        if ($num > 0) {
2836
            $list[] = [
2837
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2838
                'id' => api_htmlentities(get_lang('Interaction ID'), ENT_QUOTES),
2839
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2840
                'time' => api_htmlentities(get_lang('Time (finished at...)'), ENT_QUOTES),
2841
                'correct_responses' => api_htmlentities(get_lang('Correct answers'), ENT_QUOTES),
2842
                'student_response' => api_htmlentities(get_lang('Learner answers'), ENT_QUOTES),
2843
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2844
                'latency' => api_htmlentities(get_lang('Time spent'), ENT_QUOTES),
2845
                'student_response_formatted' => '',
2846
            ];
2847
            while ($row = Database::fetch_array($res)) {
2848
                $studentResponseFormatted = urldecode($row['student_response']);
2849
                $content_student_response = explode('__|', $studentResponseFormatted);
2850
                if (count($content_student_response) > 0) {
2851
                    if (count($content_student_response) >= 3) {
2852
                        // Pop the element off the end of array.
2853
                        array_pop($content_student_response);
2854
                    }
2855
                    $studentResponseFormatted = implode(',', $content_student_response);
2856
                }
2857
2858
                $list[] = [
2859
                    'order_id' => $row['order_id'] + 1,
2860
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2861
                    'type' => $row['interaction_type'],
2862
                    'time' => $row['completion_time'],
2863
                    'correct_responses' => '', // Hide correct responses from students.
2864
                    'student_response' => $row['student_response'],
2865
                    'result' => $row['result'],
2866
                    'latency' => $row['latency'],
2867
                    'student_response_formatted' => $studentResponseFormatted,
2868
                ];
2869
            }
2870
        }
2871
2872
        return $list;
2873
    }
2874
2875
    /**
2876
     * Return the number of objectives for the given learnpath Item View ID.
2877
     * This method can be used as static.
2878
     *
2879
     * @param int $lp_iv_id  Item View ID
2880
     * @param int $course_id Course ID
2881
     *
2882
     * @return int Number of objectives
2883
     */
2884
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2885
    {
2886
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2887
        $course_id = (int) $course_id;
2888
        $lp_iv_id = (int) $lp_iv_id;
2889
        $sql = "SELECT count(*) FROM $table
2890
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2891
        //@todo seems that this always returns 0
2892
        $res = Database::query($sql);
2893
        $num = 0;
2894
        if (Database::num_rows($res)) {
2895
            $row = Database::fetch_array($res);
2896
            $num = $row[0];
2897
        }
2898
2899
        return $num;
2900
    }
2901
2902
    /**
2903
     * Return the objectives as an array for the given lp_iv_id.
2904
     * This method can be used as static.
2905
     *
2906
     * @param int $lpItemViewId Learnpath Item View ID
2907
     * @param int $course_id
2908
     *
2909
     * @return array
2910
     *
2911
     * @todo    Translate labels
2912
     */
2913
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
2914
    {
2915
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2916
        $lpItemViewId = (int) $lpItemViewId;
2917
2918
        if (empty($course_id) || empty($lpItemViewId)) {
2919
            return [];
2920
        }
2921
2922
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2923
        $sql = "SELECT * FROM $table
2924
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
2925
                ORDER BY order_id ASC";
2926
        $res = Database::query($sql);
2927
        $num = Database::num_rows($res);
2928
        $list = [];
2929
        if ($num > 0) {
2930
            $list[] = [
2931
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2932
                'objective_id' => api_htmlentities(get_lang('Objective ID'), ENT_QUOTES),
2933
                'score_raw' => api_htmlentities(get_lang('Objective raw score'), ENT_QUOTES),
2934
                'score_max' => api_htmlentities(get_lang('Objective max score'), ENT_QUOTES),
2935
                'score_min' => api_htmlentities(get_lang('Objective min score'), ENT_QUOTES),
2936
                'status' => api_htmlentities(get_lang('Objective status'), ENT_QUOTES),
2937
            ];
2938
            while ($row = Database::fetch_array($res)) {
2939
                $list[] = [
2940
                    'order_id' => $row['order_id'] + 1,
2941
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
2942
                    'score_raw' => $row['score_raw'],
2943
                    'score_max' => $row['score_max'],
2944
                    'score_min' => $row['score_min'],
2945
                    'status' => $row['status'],
2946
                ];
2947
            }
2948
        }
2949
2950
        return $list;
2951
    }
2952
2953
    /**
2954
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
2955
     * used by get_html_toc() to be ready to display.
2956
     *
2957
     * @return array TOC as a table with 4 elements per row: title, link, status and level
2958
     */
2959
    public function get_toc()
2960
    {
2961
        $toc = [];
2962
        foreach ($this->ordered_items as $item_id) {
2963
            // TODO: Change this link generation and use new function instead.
2964
            $toc[] = [
2965
                'id' => $item_id,
2966
                'title' => $this->items[$item_id]->get_title(),
2967
                'status' => $this->items[$item_id]->get_status(),
2968
                'level' => $this->items[$item_id]->get_level(),
2969
                'type' => $this->items[$item_id]->get_type(),
2970
                'description' => $this->items[$item_id]->get_description(),
2971
                'path' => $this->items[$item_id]->get_path(),
2972
                'parent' => $this->items[$item_id]->get_parent(),
2973
            ];
2974
        }
2975
2976
        return $toc;
2977
    }
2978
2979
    /**
2980
     * Returns the CSS class name associated with a given item status.
2981
     *
2982
     * @param $status string an item status
2983
     *
2984
     * @return string CSS class name
2985
     */
2986
    public static function getStatusCSSClassName($status)
2987
    {
2988
        if (array_key_exists($status, self::STATUS_CSS_CLASS_NAME)) {
2989
            return self::STATUS_CSS_CLASS_NAME[$status];
2990
        }
2991
2992
        return '';
2993
    }
2994
2995
    /**
2996
     * Generate the tree of contents for this learnpath as an associative array tree
2997
     * with keys id, title, status, type, description, path, parent_id, children
2998
     * (title and descriptions as secured)
2999
     * and clues for CSS class composition:
3000
     *  - booleans is_current, is_parent_of_current, is_chapter
3001
     *  - string status_css_class_name.
3002
     *
3003
     * @param $parentId int restrict returned list to children of this parent
3004
     *
3005
     * @return array TOC as a table
3006
     */
3007
    public function getTOCTree($parentId = 0)
3008
    {
3009
        $toc = [];
3010
        $currentItemId = $this->get_current_item_id();
3011
3012
        foreach ($this->ordered_items as $itemId) {
3013
            $item = $this->items[$itemId];
3014
            if ($item->get_parent() == $parentId) {
3015
                $title = $item->get_title();
3016
                if (empty($title)) {
3017
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $itemId);
3018
                }
3019
3020
                $itemData = [
3021
                    'id' => $itemId,
3022
                    'title' => Security::remove_XSS($title),
3023
                    'status' => $item->get_status(),
3024
                    'level' => $item->get_level(), // FIXME should not be needed
3025
                    'type' => $item->get_type(),
3026
                    'description' => Security::remove_XSS($item->get_description()),
3027
                    'path' => $item->get_path(),
3028
                    'parent_id' => $item->get_parent(),
3029
                    'children' => $this->getTOCTree($itemId),
3030
                    'is_current' => ($itemId == $currentItemId),
3031
                    'is_parent_of_current' => false,
3032
                    'is_chapter' => in_array($item->get_type(), self::getChapterTypes()),
3033
                    'status_css_class_name' => $this->getStatusCSSClassName($item->get_status()),
3034
                    'current_id' => $currentItemId, // FIXME should not be needed, not a property of item
3035
                ];
3036
3037
                if (!empty($itemData['children'])) {
3038
                    foreach ($itemData['children'] as $child) {
3039
                        if ($child['is_current'] || $child['is_parent_of_current']) {
3040
                            $itemData['is_parent_of_current'] = true;
3041
                            break;
3042
                        }
3043
                    }
3044
                }
3045
3046
                $toc[] = $itemData;
3047
            }
3048
        }
3049
3050
        return $toc;
3051
    }
3052
3053
    /**
3054
     * Generate and return the table of contents for this learnpath. The JS
3055
     * table returned is used inside of scorm_api.php.
3056
     *
3057
     * @param string $varname
3058
     *
3059
     * @return string A JS array variable construction
3060
     */
3061
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
3062
    {
3063
        $toc = $varname.' = new Array();';
3064
        foreach ($this->ordered_items as $item_id) {
3065
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
3066
        }
3067
3068
        return $toc;
3069
    }
3070
3071
    /**
3072
     * Gets the learning path type.
3073
     *
3074
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
3075
     *
3076
     * @return mixed Type ID or name, depending on the parameter
3077
     */
3078
    public function get_type($get_name = false)
3079
    {
3080
        $res = false;
3081
        if (!empty($this->type) && (!$get_name)) {
3082
            $res = $this->type;
3083
        }
3084
3085
        return $res;
3086
    }
3087
3088
    /**
3089
     * Gets the learning path type as static method.
3090
     *
3091
     * @param int $lp_id
3092
     *
3093
     * @return mixed Type ID or name, depending on the parameter
3094
     */
3095
    public static function get_type_static($lp_id = 0)
3096
    {
3097
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3098
        $lp_id = (int) $lp_id;
3099
        $sql = "SELECT lp_type FROM $tbl_lp
3100
                WHERE iid = $lp_id";
3101
        $res = Database::query($sql);
3102
        if (false === $res) {
3103
            return null;
3104
        }
3105
        if (Database::num_rows($res) <= 0) {
3106
            return null;
3107
        }
3108
        $row = Database::fetch_array($res);
3109
3110
        return $row['lp_type'];
3111
    }
3112
3113
    /**
3114
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3115
     * This method can be used as abstract and is recursive.
3116
     *
3117
     * @param int $lp        Learnpath ID
3118
     * @param int $parent    Parent ID of the items to look for
3119
     * @param int $course_id
3120
     *
3121
     * @return array Ordered list of item IDs (empty array on error)
3122
     */
3123
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3124
    {
3125
        if (empty($course_id)) {
3126
            $course_id = api_get_course_int_id();
3127
        } else {
3128
            $course_id = (int) $course_id;
3129
        }
3130
        $list = [];
3131
3132
        if (empty($lp)) {
3133
            return $list;
3134
        }
3135
3136
        $lp = (int) $lp;
3137
        $parent = (int) $parent;
3138
3139
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3140
        $sql = "SELECT iid FROM $tbl_lp_item
3141
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3142
                ORDER BY display_order";
3143
3144
        $res = Database::query($sql);
3145
        while ($row = Database::fetch_array($res)) {
3146
            $sublist = self::get_flat_ordered_items_list(
3147
                $lp,
3148
                $row['iid'],
3149
                $course_id
3150
            );
3151
            $list[] = $row['iid'];
3152
            foreach ($sublist as $item) {
3153
                $list[] = $item;
3154
            }
3155
        }
3156
3157
        return $list;
3158
    }
3159
3160
    /**
3161
     * @return array
3162
     */
3163
    public static function getChapterTypes()
3164
    {
3165
        return [
3166
            'dir',
3167
        ];
3168
    }
3169
3170
    /**
3171
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3172
     *
3173
     * @param $tree
3174
     *
3175
     * @return array HTML TOC ready to display
3176
     */
3177
    public function getParentToc($tree)
3178
    {
3179
        if (empty($tree)) {
3180
            $tree = $this->get_toc();
3181
        }
3182
        $dirTypes = self::getChapterTypes();
3183
        $myCurrentId = $this->get_current_item_id();
3184
        $listParent = [];
3185
        $listChildren = [];
3186
        $listNotParent = [];
3187
        $list = [];
3188
        foreach ($tree as $subtree) {
3189
            if (in_array($subtree['type'], $dirTypes)) {
3190
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3191
                $subtree['children'] = $listChildren;
3192
                if (!empty($subtree['children'])) {
3193
                    foreach ($subtree['children'] as $subItem) {
3194
                        if ($subItem['id'] == $this->current) {
3195
                            $subtree['parent_current'] = 'in';
3196
                            $subtree['current'] = 'on';
3197
                        }
3198
                    }
3199
                }
3200
                $listParent[] = $subtree;
3201
            }
3202
            if (!in_array($subtree['type'], $dirTypes) && null == $subtree['parent']) {
3203
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3204
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3205
                }
3206
3207
                $title = Security::remove_XSS($subtree['title']);
3208
                unset($subtree['title']);
3209
3210
                if (empty($title)) {
3211
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3212
                }
3213
                $classStyle = null;
3214
                if ($subtree['id'] == $this->current) {
3215
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3216
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3217
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3218
                }
3219
                $subtree['title'] = $title;
3220
                $subtree['class'] = $classStyle.' '.$cssStatus;
3221
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3222
                $subtree['current_id'] = $myCurrentId;
3223
                $listNotParent[] = $subtree;
3224
            }
3225
        }
3226
3227
        $list['are_parents'] = $listParent;
3228
        $list['not_parents'] = $listNotParent;
3229
3230
        return $list;
3231
    }
3232
3233
    /**
3234
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3235
     *
3236
     * @param array $tree
3237
     * @param int   $id
3238
     * @param bool  $parent
3239
     *
3240
     * @return array HTML TOC ready to display
3241
     */
3242
    public function getChildrenToc($tree, $id, $parent = true)
3243
    {
3244
        if (empty($tree)) {
3245
            $tree = $this->get_toc();
3246
        }
3247
3248
        $dirTypes = self::getChapterTypes();
3249
        $currentItemId = $this->get_current_item_id();
3250
        $list = [];
3251
3252
        foreach ($tree as $subtree) {
3253
            $subtree['tree'] = null;
3254
3255
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3256
                if ($subtree['id'] == $this->current) {
3257
                    $subtree['current'] = 'active';
3258
                } else {
3259
                    $subtree['current'] = null;
3260
                }
3261
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3262
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3263
                }
3264
3265
                $title = Security::remove_XSS($subtree['title']);
3266
                unset($subtree['title']);
3267
                if (empty($title)) {
3268
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3269
                }
3270
3271
                $classStyle = null;
3272
                if ($subtree['id'] == $this->current) {
3273
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3274
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3275
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3276
                }
3277
3278
                if (in_array($subtree['type'], $dirTypes)) {
3279
                    $subtree['title'] = stripslashes($title);
3280
                } else {
3281
                    $subtree['title'] = $title;
3282
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3283
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3284
                    $subtree['current_id'] = $currentItemId;
3285
                }
3286
                $list[] = $subtree;
3287
            }
3288
        }
3289
3290
        return $list;
3291
    }
3292
3293
    /**
3294
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3295
     *
3296
     * @param array $toc_list
3297
     *
3298
     * @return array HTML TOC ready to display
3299
     */
3300
    public function getListArrayToc($toc_list = [])
3301
    {
3302
        if (empty($toc_list)) {
3303
            $toc_list = $this->get_toc();
3304
        }
3305
        // Temporary variables.
3306
        $currentItemId = $this->get_current_item_id();
3307
        $list = [];
3308
        $arrayList = [];
3309
3310
        foreach ($toc_list as $item) {
3311
            $list['id'] = $item['id'];
3312
            $list['status'] = $item['status'];
3313
            $cssStatus = null;
3314
3315
            if (array_key_exists($item['status'], self::STATUS_CSS_CLASS_NAME)) {
3316
                $cssStatus = self::STATUS_CSS_CLASS_NAME[$item['status']];
3317
            }
3318
3319
            $classStyle = ' ';
3320
            $dirTypes = self::getChapterTypes();
3321
3322
            if (in_array($item['type'], $dirTypes)) {
3323
                $classStyle = 'scorm_item_section ';
3324
            }
3325
            if ($item['id'] == $this->current) {
3326
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3327
            } elseif (!in_array($item['type'], $dirTypes)) {
3328
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3329
            }
3330
            $title = $item['title'];
3331
            if (empty($title)) {
3332
                $title = self::rl_get_resource_name(
3333
                    api_get_course_id(),
3334
                    $this->get_id(),
3335
                    $item['id']
3336
                );
3337
            }
3338
            $title = Security::remove_XSS($item['title']);
3339
3340
            if (empty($item['description'])) {
3341
                $list['description'] = $title;
3342
            } else {
3343
                $list['description'] = $item['description'];
3344
            }
3345
3346
            $list['class'] = $classStyle.' '.$cssStatus;
3347
            $list['level'] = $item['level'];
3348
            $list['type'] = $item['type'];
3349
3350
            if (in_array($item['type'], $dirTypes)) {
3351
                $list['css_level'] = 'level_'.$item['level'];
3352
            } else {
3353
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3354
            }
3355
3356
            if (in_array($item['type'], $dirTypes)) {
3357
                $list['title'] = stripslashes($title);
3358
            } else {
3359
                $list['title'] = stripslashes($title);
3360
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3361
                $list['current_id'] = $currentItemId;
3362
            }
3363
            $arrayList[] = $list;
3364
        }
3365
3366
        return $arrayList;
3367
    }
3368
3369
    /**
3370
     * Returns an HTML-formatted string ready to display with teacher buttons
3371
     * in LP view menu.
3372
     *
3373
     * @return string HTML TOC ready to display
3374
     */
3375
    public function get_teacher_toc_buttons()
3376
    {
3377
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3378
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3379
        $html = '';
3380
        if ($isAllow && false == $hideIcons) {
3381
            if ($this->get_lp_session_id() == api_get_session_id()) {
3382
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3383
                $html .= '<div class="btn-group">';
3384
                $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'>".
3385
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3386
                $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'>".
3387
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3388
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3389
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3390
                $html .= '</div>';
3391
                $html .= '</div>';
3392
            }
3393
        }
3394
3395
        return $html;
3396
    }
3397
3398
    /**
3399
     * Gets the learnpath maker name - generally the editor's name.
3400
     *
3401
     * @return string Learnpath maker name
3402
     */
3403
    public function get_maker()
3404
    {
3405
        if (!empty($this->maker)) {
3406
            return $this->maker;
3407
        }
3408
3409
        return '';
3410
    }
3411
3412
    /**
3413
     * Gets the learnpath name/title.
3414
     *
3415
     * @return string Learnpath name/title
3416
     */
3417
    public function get_name()
3418
    {
3419
        if (!empty($this->name)) {
3420
            return $this->name;
3421
        }
3422
3423
        return 'N/A';
3424
    }
3425
3426
    /**
3427
     * @return string
3428
     */
3429
    public function getNameNoTags()
3430
    {
3431
        return strip_tags($this->get_name());
3432
    }
3433
3434
    /**
3435
     * Gets a link to the resource from the present location, depending on item ID.
3436
     *
3437
     * @param string $type         Type of link expected
3438
     * @param int    $item_id      Learnpath item ID
3439
     * @param bool   $provided_toc
3440
     *
3441
     * @return string $provided_toc Link to the lp_item resource
3442
     */
3443
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
3444
    {
3445
        $course_id = $this->get_course_int_id();
3446
        $item_id = (int) $item_id;
3447
3448
        if (empty($item_id)) {
3449
            $item_id = $this->get_current_item_id();
3450
3451
            if (empty($item_id)) {
3452
                //still empty, this means there was no item_id given and we are not in an object context or
3453
                //the object property is empty, return empty link
3454
                $this->first();
3455
3456
                return '';
3457
            }
3458
        }
3459
3460
        $file = '';
3461
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3462
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3463
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3464
3465
        $sql = "SELECT
3466
                    l.lp_type as ltype,
3467
                    l.path as lpath,
3468
                    li.item_type as litype,
3469
                    li.path as lipath,
3470
                    li.parameters as liparams
3471
        		FROM $lp_table l
3472
                INNER JOIN $lp_item_table li
3473
                ON (li.lp_id = l.iid)
3474
        		WHERE
3475
        		    li.iid = $item_id
3476
        		";
3477
        $res = Database::query($sql);
3478
        if (Database::num_rows($res) > 0) {
3479
            $row = Database::fetch_array($res);
3480
            $lp_type = $row['ltype'];
3481
            $lp_path = $row['lpath'];
3482
            $lp_item_type = $row['litype'];
3483
            $lp_item_path = $row['lipath'];
3484
            $lp_item_params = $row['liparams'];
3485
3486
            if (empty($lp_item_params) && false !== strpos($lp_item_path, '?')) {
3487
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3488
            }
3489
            //$sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3490
            if ('http' === $type) {
3491
                //web path
3492
                //$course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3493
            } else {
3494
                //$course_path = $sys_course_path; //system path
3495
            }
3496
3497
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3498
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3499
            if (in_array(
3500
                $lp_item_type,
3501
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3502
            )
3503
            ) {
3504
                $lp_type = 1;
3505
            }
3506
3507
            // Now go through the specific cases to get the end of the path
3508
            // @todo Use constants instead of int values.
3509
            switch ($lp_type) {
3510
                case 1:
3511
                    $file = self::rl_get_resource_link_for_learnpath(
3512
                        $course_id,
3513
                        $this->get_id(),
3514
                        $item_id,
3515
                        $this->get_view_id()
3516
                    );
3517
                    switch ($lp_item_type) {
3518
                        case 'document':
3519
                            // Shows a button to download the file instead of just downloading the file directly.
3520
                            $documentPathInfo = pathinfo($file);
3521
                            if (isset($documentPathInfo['extension'])) {
3522
                                $parsed = parse_url($documentPathInfo['extension']);
3523
                                if (isset($parsed['path'])) {
3524
                                    $extension = $parsed['path'];
3525
                                    $extensionsToDownload = [
3526
                                        'zip',
3527
                                        'ppt',
3528
                                        'pptx',
3529
                                        'ods',
3530
                                        'xlsx',
3531
                                        'xls',
3532
                                        'csv',
3533
                                        'doc',
3534
                                        'docx',
3535
                                        'dot',
3536
                                    ];
3537
3538
                                    if (in_array($extension, $extensionsToDownload)) {
3539
                                        $file = api_get_path(WEB_CODE_PATH).
3540
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
3541
                                    }
3542
                                }
3543
                            }
3544
                            break;
3545
                        case 'dir':
3546
                            $file = 'lp_content.php?type=dir';
3547
                            break;
3548
                        case 'link':
3549
                            if (Link::is_youtube_link($file)) {
3550
                                $src = Link::get_youtube_video_id($file);
3551
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3552
                            } elseif (Link::isVimeoLink($file)) {
3553
                                $src = Link::getVimeoLinkId($file);
3554
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3555
                            } else {
3556
                                // If the current site is HTTPS and the link is
3557
                                // HTTP, browsers will refuse opening the link
3558
                                $urlId = api_get_current_access_url_id();
3559
                                $url = api_get_access_url($urlId, false);
3560
                                $protocol = substr($url['url'], 0, 5);
3561
                                if ('https' === $protocol) {
3562
                                    $linkProtocol = substr($file, 0, 5);
3563
                                    if ('http:' === $linkProtocol) {
3564
                                        //this is the special intervention case
3565
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3566
                                    }
3567
                                }
3568
                            }
3569
                            break;
3570
                        case 'quiz':
3571
                            // Check how much attempts of a exercise exits in lp
3572
                            $lp_item_id = $this->get_current_item_id();
3573
                            $lp_view_id = $this->get_view_id();
3574
3575
                            $prevent_reinit = null;
3576
                            if (isset($this->items[$this->current])) {
3577
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3578
                            }
3579
3580
                            if (empty($provided_toc)) {
3581
                                $list = $this->get_toc();
3582
                            } else {
3583
                                $list = $provided_toc;
3584
                            }
3585
3586
                            $type_quiz = false;
3587
                            foreach ($list as $toc) {
3588
                                if ($toc['id'] == $lp_item_id && 'quiz' == $toc['type']) {
3589
                                    $type_quiz = true;
3590
                                }
3591
                            }
3592
3593
                            if ($type_quiz) {
3594
                                $lp_item_id = (int) $lp_item_id;
3595
                                $lp_view_id = (int) $lp_view_id;
3596
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3597
                                        WHERE
3598
                                            c_id = $course_id AND
3599
                                            lp_item_id='".$lp_item_id."' AND
3600
                                            lp_view_id ='".$lp_view_id."' AND
3601
                                            status='completed'";
3602
                                $result = Database::query($sql);
3603
                                $row_count = Database:: fetch_row($result);
3604
                                $count_item_view = (int) $row_count[0];
3605
                                $not_multiple_attempt = 0;
3606
                                if (1 === $prevent_reinit && $count_item_view > 0) {
3607
                                    $not_multiple_attempt = 1;
3608
                                }
3609
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3610
                            }
3611
                            break;
3612
                    }
3613
3614
                    $tmp_array = explode('/', $file);
3615
                    $document_name = $tmp_array[count($tmp_array) - 1];
3616
                    if (strpos($document_name, '_DELETED_')) {
3617
                        $file = 'blank.php?error=document_deleted';
3618
                    }
3619
                    break;
3620
                case 2:
3621
                    if ('dir' !== $lp_item_type) {
3622
                        // Quite complex here:
3623
                        // We want to make sure 'http://' (and similar) links can
3624
                        // be loaded as is (withouth the Chamilo path in front) but
3625
                        // some contents use this form: resource.htm?resource=http://blablabla
3626
                        // which means we have to find a protocol at the path's start, otherwise
3627
                        // it should not be considered as an external URL.
3628
                        // if ($this->prerequisites_match($item_id)) {
3629
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
3630
                            if ($this->debug > 2) {
3631
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3632
                            }
3633
                            // Distant url, return as is.
3634
                            $file = $lp_item_path;
3635
                        } else {
3636
                            if ($this->debug > 2) {
3637
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3638
                            }
3639
                            // Prevent getting untranslatable urls.
3640
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3641
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3642
                            // Prepare the path.
3643
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3644
                            // TODO: Fix this for urls with protocol header.
3645
                            $file = str_replace('//', '/', $file);
3646
                            $file = str_replace(':/', '://', $file);
3647
                            if ('/' == substr($lp_path, -1)) {
3648
                                $lp_path = substr($lp_path, 0, -1);
3649
                            }
3650
3651
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3652
                                // if file not found.
3653
                                $decoded = html_entity_decode($lp_item_path);
3654
                                list($decoded) = explode('?', $decoded);
3655
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3656
                                    $file = self::rl_get_resource_link_for_learnpath(
3657
                                        $course_id,
3658
                                        $this->get_id(),
3659
                                        $item_id,
3660
                                        $this->get_view_id()
3661
                                    );
3662
                                    if (empty($file)) {
3663
                                        $file = 'blank.php?error=document_not_found';
3664
                                    } else {
3665
                                        $tmp_array = explode('/', $file);
3666
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3667
                                        if (strpos($document_name, '_DELETED_')) {
3668
                                            $file = 'blank.php?error=document_deleted';
3669
                                        } else {
3670
                                            $file = 'blank.php?error=document_not_found';
3671
                                        }
3672
                                    }
3673
                                } else {
3674
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3675
                                }
3676
                            }
3677
                        }
3678
3679
                        // We want to use parameters if they were defined in the imsmanifest
3680
                        if (false === strpos($file, 'blank.php')) {
3681
                            $lp_item_params = ltrim($lp_item_params, '?');
3682
                            $file .= (false === strstr($file, '?') ? '?' : '').$lp_item_params;
3683
                        }
3684
                    } else {
3685
                        $file = 'lp_content.php?type=dir';
3686
                    }
3687
                    break;
3688
                case 3:
3689
                    // Formatting AICC HACP append URL.
3690
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3691
                    if (!empty($lp_item_params)) {
3692
                        $aicc_append .= $lp_item_params.'&';
3693
                    }
3694
                    if ('dir' !== $lp_item_type) {
3695
                        // Quite complex here:
3696
                        // We want to make sure 'http://' (and similar) links can
3697
                        // be loaded as is (withouth the Chamilo path in front) but
3698
                        // some contents use this form: resource.htm?resource=http://blablabla
3699
                        // which means we have to find a protocol at the path's start, otherwise
3700
                        // it should not be considered as an external URL.
3701
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
3702
                            if ($this->debug > 2) {
3703
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3704
                            }
3705
                            // Distant url, return as is.
3706
                            $file = $lp_item_path;
3707
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3708
                            /*
3709
                            if (stristr($file,'<servername>') !== false) {
3710
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3711
                            }
3712
                            */
3713
                            if (false !== stripos($file, '<servername>')) {
3714
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3715
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3716
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3717
                            }
3718
3719
                            $file .= $aicc_append;
3720
                        } else {
3721
                            if ($this->debug > 2) {
3722
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3723
                            }
3724
                            // Prevent getting untranslatable urls.
3725
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3726
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3727
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3728
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3729
                            // TODO: Fix this for urls with protocol header.
3730
                            $file = str_replace('//', '/', $file);
3731
                            $file = str_replace(':/', '://', $file);
3732
                            $file .= $aicc_append;
3733
                        }
3734
                    } else {
3735
                        $file = 'lp_content.php?type=dir';
3736
                    }
3737
                    break;
3738
                case 4:
3739
                    break;
3740
                default:
3741
                    break;
3742
            }
3743
            // Replace &amp; by & because &amp; will break URL with params
3744
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3745
        }
3746
        if ($this->debug > 2) {
3747
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3748
        }
3749
3750
        return $file;
3751
    }
3752
3753
    /**
3754
     * Gets the latest usable view or generate a new one.
3755
     *
3756
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3757
     * @param int $userId      The user ID, as $this->get_user_id() is not always available
3758
     *
3759
     * @return int DB lp_view id
3760
     */
3761
    public function get_view($attempt_num = 0, $userId = null)
3762
    {
3763
        $search = '';
3764
        // Use $attempt_num to enable multi-views management (disabled so far).
3765
        if (0 != $attempt_num && intval(strval($attempt_num)) == $attempt_num) {
3766
            $search = 'AND view_count = '.$attempt_num;
3767
        }
3768
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3769
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3770
3771
        $course_id = api_get_course_int_id();
3772
        $sessionId = api_get_session_id();
3773
3774
        // Check user ID.
3775
        if (empty($userId)) {
3776
            if (empty($this->get_user_id())) {
3777
                $this->error = 'User ID is empty in learnpath::get_view()';
3778
3779
                return null;
3780
            } else {
3781
                $userId = $this->get_user_id();
3782
            }
3783
        }
3784
3785
        $sql = "SELECT iid, view_count FROM $lp_view_table
3786
        		WHERE
3787
        		    c_id = $course_id AND
3788
        		    lp_id = ".$this->get_id()." AND
3789
        		    user_id = ".$userId." AND
3790
        		    session_id = $sessionId
3791
        		    $search
3792
                ORDER BY view_count DESC";
3793
        $res = Database::query($sql);
3794
        if (Database::num_rows($res) > 0) {
3795
            $row = Database::fetch_array($res);
3796
            $this->lp_view_id = $row['iid'];
3797
        } elseif (!api_is_invitee()) {
3798
            // There is no database record, create one.
3799
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3800
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3801
            Database::query($sql);
3802
            $id = Database::insert_id();
3803
            $this->lp_view_id = $id;
3804
        }
3805
3806
        return $this->lp_view_id;
3807
    }
3808
3809
    /**
3810
     * Gets the current view id.
3811
     *
3812
     * @return int View ID (from lp_view)
3813
     */
3814
    public function get_view_id()
3815
    {
3816
        if (!empty($this->lp_view_id)) {
3817
            return (int) $this->lp_view_id;
3818
        }
3819
3820
        return 0;
3821
    }
3822
3823
    /**
3824
     * Gets the update queue.
3825
     *
3826
     * @return array Array containing IDs of items to be updated by JavaScript
3827
     */
3828
    public function get_update_queue()
3829
    {
3830
        return $this->update_queue;
3831
    }
3832
3833
    /**
3834
     * Gets the user ID.
3835
     *
3836
     * @return int User ID
3837
     */
3838
    public function get_user_id()
3839
    {
3840
        if (!empty($this->user_id)) {
3841
            return (int) $this->user_id;
3842
        }
3843
3844
        return false;
3845
    }
3846
3847
    /**
3848
     * Checks if any of the items has an audio element attached.
3849
     *
3850
     * @return bool True or false
3851
     */
3852
    public function has_audio()
3853
    {
3854
        $has = false;
3855
        foreach ($this->items as $i => $item) {
3856
            if (!empty($this->items[$i]->audio)) {
3857
                $has = true;
3858
                break;
3859
            }
3860
        }
3861
3862
        return $has;
3863
    }
3864
3865
    /**
3866
     * Moves an item up and down at its level.
3867
     *
3868
     * @param int    $id        Item to move up and down
3869
     * @param string $direction Direction 'up' or 'down'
3870
     *
3871
     * @return bool|int
3872
     */
3873
    public function move_item($id, $direction)
3874
    {
3875
        $course_id = api_get_course_int_id();
3876
        if (empty($id) || empty($direction)) {
3877
            return false;
3878
        }
3879
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3880
        $sql_sel = "SELECT *
3881
                    FROM $tbl_lp_item
3882
                    WHERE
3883
                        iid = $id
3884
                    ";
3885
        $res_sel = Database::query($sql_sel);
3886
        // Check if elem exists.
3887
        if (Database::num_rows($res_sel) < 1) {
3888
            return false;
3889
        }
3890
        // Gather data.
3891
        $row = Database::fetch_array($res_sel);
3892
        $previous = $row['previous_item_id'];
3893
        $next = $row['next_item_id'];
3894
        $display = $row['display_order'];
3895
        $parent = $row['parent_item_id'];
3896
        $lp = $row['lp_id'];
3897
        // Update the item (switch with previous/next one).
3898
        switch ($direction) {
3899
            case 'up':
3900
                if ($display > 1) {
3901
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3902
                                 WHERE iid = $previous";
3903
                    $res_sel2 = Database::query($sql_sel2);
3904
                    if (Database::num_rows($res_sel2) < 1) {
3905
                        $previous_previous = 0;
3906
                    }
3907
                    // Gather data.
3908
                    $row2 = Database::fetch_array($res_sel2);
3909
                    $previous_previous = $row2['previous_item_id'];
3910
                    // Update previous_previous item (switch "next" with current).
3911
                    if (0 != $previous_previous) {
3912
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3913
                                        next_item_id = $id
3914
                                    WHERE iid = $previous_previous";
3915
                        Database::query($sql_upd2);
3916
                    }
3917
                    // Update previous item (switch with current).
3918
                    if (0 != $previous) {
3919
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3920
                                    next_item_id = $next,
3921
                                    previous_item_id = $id,
3922
                                    display_order = display_order +1
3923
                                    WHERE iid = $previous";
3924
                        Database::query($sql_upd2);
3925
                    }
3926
3927
                    // Update current item (switch with previous).
3928
                    if (0 != $id) {
3929
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3930
                                        next_item_id = $previous,
3931
                                        previous_item_id = $previous_previous,
3932
                                        display_order = display_order-1
3933
                                    WHERE c_id = ".$course_id." AND id = $id";
3934
                        Database::query($sql_upd2);
3935
                    }
3936
                    // Update next item (new previous item).
3937
                    if (!empty($next)) {
3938
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
3939
                                     WHERE iid = $next";
3940
                        Database::query($sql_upd2);
3941
                    }
3942
                    $display = $display - 1;
3943
                }
3944
                break;
3945
            case 'down':
3946
                if (0 != $next) {
3947
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3948
                                 WHERE iid = $next";
3949
                    $res_sel2 = Database::query($sql_sel2);
3950
                    if (Database::num_rows($res_sel2) < 1) {
3951
                        $next_next = 0;
3952
                    }
3953
                    // Gather data.
3954
                    $row2 = Database::fetch_array($res_sel2);
3955
                    $next_next = $row2['next_item_id'];
3956
                    // Update previous item (switch with current).
3957
                    if (0 != $previous) {
3958
                        $sql_upd2 = "UPDATE $tbl_lp_item
3959
                                     SET next_item_id = $next
3960
                                     WHERE iid = $previous";
3961
                        Database::query($sql_upd2);
3962
                    }
3963
                    // Update current item (switch with previous).
3964
                    if (0 != $id) {
3965
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3966
                                     previous_item_id = $next,
3967
                                     next_item_id = $next_next,
3968
                                     display_order = display_order + 1
3969
                                     WHERE iid = $id";
3970
                        Database::query($sql_upd2);
3971
                    }
3972
3973
                    // Update next item (new previous item).
3974
                    if (0 != $next) {
3975
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3976
                                     previous_item_id = $previous,
3977
                                     next_item_id = $id,
3978
                                     display_order = display_order-1
3979
                                     WHERE iid = $next";
3980
                        Database::query($sql_upd2);
3981
                    }
3982
3983
                    // Update next_next item (switch "previous" with current).
3984
                    if (0 != $next_next) {
3985
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3986
                                     previous_item_id = $id
3987
                                     WHERE iid = $next_next";
3988
                        Database::query($sql_upd2);
3989
                    }
3990
                    $display = $display + 1;
3991
                }
3992
                break;
3993
            default:
3994
                return false;
3995
        }
3996
3997
        return $display;
3998
    }
3999
4000
    /**
4001
     * Move a LP up (display_order).
4002
     *
4003
     * @param int $lp_id      Learnpath ID
4004
     * @param int $categoryId Category ID
4005
     *
4006
     * @return bool
4007
     */
4008
    public static function move_up($lp_id, $categoryId = 0)
4009
    {
4010
        $courseId = api_get_course_int_id();
4011
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4012
4013
        $categoryCondition = '';
4014
        if (!empty($categoryId)) {
4015
            $categoryId = (int) $categoryId;
4016
            $categoryCondition = " AND category_id = $categoryId";
4017
        }
4018
        $sql = "SELECT * FROM $lp_table
4019
                WHERE c_id = $courseId
4020
                $categoryCondition
4021
                ORDER BY display_order";
4022
        $res = Database::query($sql);
4023
        if (false === $res) {
4024
            return false;
4025
        }
4026
4027
        $lps = [];
4028
        $lp_order = [];
4029
        $num = Database::num_rows($res);
4030
        // First check the order is correct, globally (might be wrong because
4031
        // of versions < 1.8.4)
4032
        if ($num > 0) {
4033
            $i = 1;
4034
            while ($row = Database::fetch_array($res)) {
4035
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
4036
                    $sql = "UPDATE $lp_table SET display_order = $i
4037
                            WHERE iid = ".$row['iid'];
4038
                    Database::query($sql);
4039
                }
4040
                $row['display_order'] = $i;
4041
                $lps[$row['iid']] = $row;
4042
                $lp_order[$i] = $row['iid'];
4043
                $i++;
4044
            }
4045
        }
4046
        if ($num > 1) { // If there's only one element, no need to sort.
4047
            $order = $lps[$lp_id]['display_order'];
4048
            if ($order > 1) { // If it's the first element, no need to move up.
4049
                $sql = "UPDATE $lp_table SET display_order = $order
4050
                        WHERE iid = ".$lp_order[$order - 1];
4051
                Database::query($sql);
4052
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
4053
                        WHERE iid = $lp_id";
4054
                Database::query($sql);
4055
            }
4056
        }
4057
4058
        return true;
4059
    }
4060
4061
    /**
4062
     * Move a learnpath down (display_order).
4063
     *
4064
     * @param int $lp_id      Learnpath ID
4065
     * @param int $categoryId Category ID
4066
     *
4067
     * @return bool
4068
     */
4069
    public static function move_down($lp_id, $categoryId = 0)
4070
    {
4071
        $courseId = api_get_course_int_id();
4072
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4073
4074
        $categoryCondition = '';
4075
        if (!empty($categoryId)) {
4076
            $categoryId = (int) $categoryId;
4077
            $categoryCondition = " AND category_id = $categoryId";
4078
        }
4079
4080
        $sql = "SELECT * FROM $lp_table
4081
                WHERE c_id = $courseId
4082
                $categoryCondition
4083
                ORDER BY display_order";
4084
        $res = Database::query($sql);
4085
        if (false === $res) {
4086
            return false;
4087
        }
4088
        $lps = [];
4089
        $lp_order = [];
4090
        $num = Database::num_rows($res);
4091
        $max = 0;
4092
        // First check the order is correct, globally (might be wrong because
4093
        // of versions < 1.8.4).
4094
        if ($num > 0) {
4095
            $i = 1;
4096
            while ($row = Database::fetch_array($res)) {
4097
                $max = $i;
4098
                if ($row['display_order'] != $i) {
4099
                    // If we find a gap in the order, we need to fix it.
4100
                    $sql = "UPDATE $lp_table SET display_order = $i
4101
                              WHERE iid = ".$row['iid'];
4102
                    Database::query($sql);
4103
                }
4104
                $row['display_order'] = $i;
4105
                $lps[$row['iid']] = $row;
4106
                $lp_order[$i] = $row['iid'];
4107
                $i++;
4108
            }
4109
        }
4110
        if ($num > 1) { // If there's only one element, no need to sort.
4111
            $order = $lps[$lp_id]['display_order'];
4112
            if ($order < $max) { // If it's the first element, no need to move up.
4113
                $sql = "UPDATE $lp_table SET display_order = $order
4114
                        WHERE iid = ".$lp_order[$order + 1];
4115
                Database::query($sql);
4116
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4117
                        WHERE iid = $lp_id";
4118
                Database::query($sql);
4119
            }
4120
        }
4121
4122
        return true;
4123
    }
4124
4125
    /**
4126
     * Updates learnpath attributes to point to the next element
4127
     * The last part is similar to set_current_item but processing the other way around.
4128
     */
4129
    public function next()
4130
    {
4131
        if ($this->debug > 0) {
4132
            error_log('In learnpath::next()', 0);
4133
        }
4134
        $this->last = $this->get_current_item_id();
4135
        $this->items[$this->last]->save(
4136
            false,
4137
            $this->prerequisites_match($this->last)
4138
        );
4139
        $this->autocomplete_parents($this->last);
4140
        $new_index = $this->get_next_index();
4141
        if ($this->debug > 2) {
4142
            error_log('New index: '.$new_index, 0);
4143
        }
4144
        $this->index = $new_index;
4145
        if ($this->debug > 2) {
4146
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4147
        }
4148
        $this->current = $this->ordered_items[$new_index];
4149
        if ($this->debug > 2) {
4150
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4151
        }
4152
    }
4153
4154
    /**
4155
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4156
     * class, this might be redefined to allow several behaviours depending on the document type.
4157
     *
4158
     * @param int $id Resource ID
4159
     */
4160
    public function open($id)
4161
    {
4162
        // TODO:
4163
        // set the current resource attribute to this resource
4164
        // switch on element type (redefine in child class?)
4165
        // set status for this item to "opened"
4166
        // start timer
4167
        // initialise score
4168
        $this->index = 0; //or = the last item seen (see $this->last)
4169
    }
4170
4171
    /**
4172
     * Check that all prerequisites are fulfilled. Returns true and an
4173
     * empty string on success, returns false
4174
     * and the prerequisite string on error.
4175
     * This function is based on the rules for aicc_script language as
4176
     * described in the SCORM 1.2 CAM documentation page 108.
4177
     *
4178
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4179
     *
4180
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4181
     *              string otherwise
4182
     */
4183
    public function prerequisites_match($itemId = null)
4184
    {
4185
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4186
        if ($allow) {
4187
            if (api_is_allowed_to_edit() ||
4188
                api_is_platform_admin(true) ||
4189
                api_is_drh() ||
4190
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4191
            ) {
4192
                return true;
4193
            }
4194
        }
4195
4196
        $debug = $this->debug;
4197
        if ($debug > 0) {
4198
            error_log('In learnpath::prerequisites_match()');
4199
        }
4200
4201
        if (empty($itemId)) {
4202
            $itemId = $this->current;
4203
        }
4204
4205
        $currentItem = $this->getItem($itemId);
4206
4207
        if ($currentItem) {
4208
            if (2 == $this->type) {
4209
                // Getting prereq from scorm
4210
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4211
            } else {
4212
                $prereq_string = $currentItem->get_prereq_string();
4213
            }
4214
4215
            if (empty($prereq_string)) {
4216
                if ($debug > 0) {
4217
                    error_log('Found prereq_string is empty return true');
4218
                }
4219
4220
                return true;
4221
            }
4222
4223
            // Clean spaces.
4224
            $prereq_string = str_replace(' ', '', $prereq_string);
4225
            if ($debug > 0) {
4226
                error_log('Found prereq_string: '.$prereq_string, 0);
4227
            }
4228
4229
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4230
            $result = $currentItem->parse_prereq(
4231
                $prereq_string,
4232
                $this->items,
4233
                $this->refs_list,
4234
                $this->get_user_id()
4235
            );
4236
4237
            if (false === $result) {
4238
                $this->set_error_msg($currentItem->prereq_alert);
4239
            }
4240
        } else {
4241
            $result = true;
4242
            if ($debug > 1) {
4243
                error_log('$this->items['.$itemId.'] was not an object', 0);
4244
            }
4245
        }
4246
4247
        if ($debug > 1) {
4248
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4249
        }
4250
4251
        return $result;
4252
    }
4253
4254
    /**
4255
     * Updates learnpath attributes to point to the previous element
4256
     * The last part is similar to set_current_item but processing the other way around.
4257
     */
4258
    public function previous()
4259
    {
4260
        $this->last = $this->get_current_item_id();
4261
        $this->items[$this->last]->save(
4262
            false,
4263
            $this->prerequisites_match($this->last)
4264
        );
4265
        $this->autocomplete_parents($this->last);
4266
        $new_index = $this->get_previous_index();
4267
        $this->index = $new_index;
4268
        $this->current = $this->ordered_items[$new_index];
4269
    }
4270
4271
    /**
4272
     * Publishes a learnpath. This basically means show or hide the learnpath
4273
     * to normal users.
4274
     * Can be used as abstract.
4275
     *
4276
     * @param int $id         Learnpath ID
4277
     * @param int $visibility New visibility (1 = visible/published, 0= invisible/draft)
4278
     *
4279
     * @return bool
4280
     */
4281
    public static function toggleVisibility($id, $visibility = 1)
4282
    {
4283
        $repo = Container::getLpRepository();
4284
        $lp = $repo->find($id);
4285
4286
        if (!$lp) {
4287
            return false;
4288
        }
4289
4290
        $visibility = (int) $visibility;
4291
4292
        if (1 === $visibility) {
4293
            $repo->setVisibilityPublished($lp);
4294
        } else {
4295
            $repo->setVisibilityDraft($lp);
4296
        }
4297
4298
        return true;
4299
4300
        /*$action = 'visible';
4301
        if (1 != $set_visibility) {
4302
            $action = 'invisible';
4303
            self::toggle_publish($lp_id, 'i');
4304
        }
4305
4306
        return api_item_property_update(
4307
            api_get_course_info(),
4308
            TOOL_LEARNPATH,
4309
            $lp_id,
4310
            $action,
4311
            api_get_user_id()
4312
        );*/
4313
    }
4314
4315
    /**
4316
     * Publishes a learnpath category.
4317
     * This basically means show or hide the learnpath category to normal users.
4318
     *
4319
     * @param int $id
4320
     * @param int $visibility
4321
     *
4322
     * @return bool
4323
     */
4324
    public static function toggleCategoryVisibility($id, $visibility = 1)
4325
    {
4326
        $repo = Container::getLpCategoryRepository();
4327
        $resource = $repo->find($id);
4328
4329
        if (!$resource) {
4330
            return false;
4331
        }
4332
4333
        $visibility = (int) $visibility;
4334
4335
        if (1 === $visibility) {
4336
            $repo->setVisibilityPublished($resource);
4337
        } else {
4338
            $repo->setVisibilityDraft($resource);
4339
            self::toggleCategoryPublish($id, 0);
4340
        }
4341
4342
        return false;
4343
        /*
4344
        $action = 'visible';
4345
        if (1 != $visibility) {
4346
            self::toggleCategoryPublish($id, 0);
4347
            $action = 'invisible';
4348
        }
4349
4350
        return api_item_property_update(
4351
            api_get_course_info(),
4352
            TOOL_LEARNPATH_CATEGORY,
4353
            $id,
4354
            $action,
4355
            api_get_user_id()
4356
        );*/
4357
    }
4358
4359
    /**
4360
     * Publishes a learnpath. This basically means show or hide the learnpath
4361
     * on the course homepage.
4362
     *
4363
     * @param int    $id            Learnpath id
4364
     * @param string $setVisibility New visibility (v/i - visible/invisible)
4365
     *
4366
     * @return bool
4367
     */
4368
    public static function togglePublish($id, $setVisibility = 'v')
4369
    {
4370
        $addShortcut = false;
4371
        if ('v' === $setVisibility) {
4372
            $addShortcut = true;
4373
        }
4374
        $repo = Container::getLpRepository();
4375
        /** @var CLp $lp */
4376
        $lp = $repo->find($id);
4377
        if (null === $lp) {
4378
            return false;
4379
        }
4380
        $repoShortcut = Container::getShortcutRepository();
4381
        $courseEntity = api_get_course_entity();
4382
4383
        if ($addShortcut) {
4384
            $repoShortcut->addShortCut($lp, $courseEntity, $courseEntity, api_get_session_entity());
4385
        } else {
4386
            $repoShortcut->removeShortCut($lp);
4387
        }
4388
4389
        return true;
4390
4391
        /*
4392
        $course_id = api_get_course_int_id();
4393
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4394
        $lp_id = (int) $lp_id;
4395
        $sql = "SELECT * FROM $tbl_lp
4396
                WHERE iid = $lp_id";
4397
        $result = Database::query($sql);
4398
4399
        if (Database::num_rows($result)) {
4400
            $row = Database::fetch_array($result);
4401
            $name = Database::escape_string($row['name']);
4402
            if ($set_visibility == 'i') {
4403
                $v = 0;
4404
            }
4405
            if ($set_visibility == 'v') {
4406
                $v = 1;
4407
            }
4408
4409
            $session_id = api_get_session_id();
4410
            $session_condition = api_get_session_condition($session_id);
4411
4412
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4413
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4414
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4415
4416
            $sql = "SELECT * FROM $tbl_tool
4417
                    WHERE
4418
                        c_id = $course_id AND
4419
                        (link = '$link' OR link = '$oldLink') AND
4420
                        image = 'scormbuilder.gif' AND
4421
                        (
4422
                            link LIKE '$link%' OR
4423
                            link LIKE '$oldLink%'
4424
                        )
4425
                        $session_condition
4426
                    ";
4427
4428
            $result = Database::query($sql);
4429
            $num = Database::num_rows($result);
4430
            if ($set_visibility == 'i' && $num > 0) {
4431
                $sql = "DELETE FROM $tbl_tool
4432
                        WHERE
4433
                            c_id = $course_id AND
4434
                            (link = '$link' OR link = '$oldLink') AND
4435
                            image='scormbuilder.gif'
4436
                            $session_condition";
4437
                Database::query($sql);
4438
            } elseif ($set_visibility == 'v' && $num == 0) {
4439
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4440
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4441
                Database::query($sql);
4442
                $insertId = Database::insert_id();
4443
                if ($insertId) {
4444
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4445
                    Database::query($sql);
4446
                }
4447
            } elseif ($set_visibility == 'v' && $num > 0) {
4448
                $sql = "UPDATE $tbl_tool SET
4449
                            c_id = $course_id,
4450
                            name = '$name',
4451
                            link = '$link',
4452
                            image = 'scormbuilder.gif',
4453
                            visibility = '$v',
4454
                            admin = '0',
4455
                            address = 'pastillegris.gif',
4456
                            added_tool = 0,
4457
                            session_id = $session_id
4458
                        WHERE
4459
                            c_id = ".$course_id." AND
4460
                            (link = '$link' OR link = '$oldLink') AND
4461
                            image='scormbuilder.gif'
4462
                            $session_condition
4463
                        ";
4464
                Database::query($sql);
4465
            } else {
4466
                // Parameter and database incompatible, do nothing, exit.
4467
                return false;
4468
            }
4469
        } else {
4470
            return false;
4471
        }*/
4472
    }
4473
4474
    /**
4475
     * Show or hide the learnpath category on the course homepage.
4476
     *
4477
     * @param int $id
4478
     * @param int $setVisibility
4479
     *
4480
     * @return bool
4481
     */
4482
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4483
    {
4484
        $setVisibility = (int) $setVisibility;
4485
        $addShortcut = false;
4486
        if (1 === $setVisibility) {
4487
            $addShortcut = true;
4488
        }
4489
4490
        $repo = Container::getLpCategoryRepository();
4491
        /** @var CLpCategory $lp */
4492
        $category = $repo->find($id);
4493
4494
        if (null === $category) {
4495
            return false;
4496
        }
4497
4498
        $repoShortcut = Container::getShortcutRepository();
4499
        if ($addShortcut) {
4500
            $courseEntity = api_get_course_entity(api_get_course_int_id());
4501
            $repoShortcut->addShortCut($category, $courseEntity, $courseEntity, api_get_session_entity());
4502
        } else {
4503
            $repoShortcut->removeShortCut($category);
4504
        }
4505
4506
        return true;
4507
4508
        $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...
4509
4510
        /** @var CLpCategory $category */
4511
        $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
4512
4513
        if (!$category) {
4514
            return false;
4515
        }
4516
4517
        if (empty($courseId)) {
4518
            return false;
4519
        }
4520
4521
        $link = self::getCategoryLinkForTool($id);
4522
4523
        /** @var CTool $tool */
4524
        $tool = $em->createQuery("
4525
                SELECT t FROM ChamiloCourseBundle:CTool t
4526
                WHERE
4527
                    t.course = :course AND
4528
                    t.link = :link1 AND
4529
                    t.image LIKE 'lp_category.%' AND
4530
                    t.link LIKE :link2
4531
                    $sessionCondition
4532
            ")
4533
            ->setParameters([
4534
                'course' => $courseId,
4535
                'link1' => $link,
4536
                'link2' => "$link%",
4537
            ])
4538
            ->getOneOrNullResult();
4539
4540
        if (0 == $setVisibility && $tool) {
4541
            $em->remove($tool);
4542
            $em->flush();
4543
4544
            return true;
4545
        }
4546
4547
        if (1 == $setVisibility && !$tool) {
4548
            $tool = new CTool();
4549
            $tool
4550
                ->setCategory('authoring')
4551
                ->setCourse(api_get_course_entity($courseId))
4552
                ->setName(strip_tags($category->getName()))
4553
                ->setLink($link)
4554
                ->setImage('lp_category.png')
4555
                ->setVisibility(1)
4556
                ->setAdmin(0)
4557
                ->setAddress('pastillegris.gif')
4558
                ->setAddedTool(0)
4559
                ->setSessionId($sessionId)
4560
                ->setTarget('_self');
4561
4562
            $em->persist($tool);
4563
            $em->flush();
4564
4565
            $tool->setId($tool->getIid());
4566
4567
            $em->persist($tool);
4568
            $em->flush();
4569
4570
            return true;
4571
        }
4572
4573
        if (1 == $setVisibility && $tool) {
4574
            $tool
4575
                ->setName(strip_tags($category->getName()))
4576
                ->setVisibility(1);
4577
4578
            $em->persist($tool);
4579
            $em->flush();
4580
4581
            return true;
4582
        }
4583
4584
        return false;
4585
    }
4586
4587
    /**
4588
     * Check if the learnpath category is visible for a user.
4589
     *
4590
     * @param int
4591
     * @param int
4592
     *
4593
     * @return bool
4594
     */
4595
    public static function categoryIsVisibleForStudent(
4596
        CLpCategory $category,
4597
        User $user,
4598
        $courseId = 0,
4599
        $sessionId = 0
4600
    ) {
4601
        if (empty($category)) {
4602
            return false;
4603
        }
4604
4605
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4606
4607
        if ($isAllowedToEdit) {
4608
            return true;
4609
        }
4610
4611
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4612
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4613
4614
        $courseInfo = api_get_course_info_by_id($courseId);
4615
4616
        $categoryVisibility = api_get_item_visibility(
4617
            $courseInfo,
4618
            TOOL_LEARNPATH_CATEGORY,
4619
            $category->getId(),
4620
            $sessionId
4621
        );
4622
4623
        if (1 !== $categoryVisibility && -1 != $categoryVisibility) {
4624
            return false;
4625
        }
4626
4627
        $subscriptionSettings = self::getSubscriptionSettings();
4628
4629
        if (false == $subscriptionSettings['allow_add_users_to_lp_category']) {
4630
            return true;
4631
        }
4632
4633
        $noUserSubscribed = false;
4634
        $noGroupSubscribed = true;
4635
        $users = $category->getUsers();
4636
        if (empty($users) || !$users->count()) {
4637
            $noUserSubscribed = true;
4638
        } elseif ($category->hasUserAdded($user)) {
4639
            return true;
4640
        }
4641
4642
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4643
        $em = Database::getManager();
4644
4645
        /** @var ItemPropertyRepository $itemRepo */
4646
        $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4647
4648
        /** @var CourseRepository $courseRepo */
4649
        $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4650
        $session = null;
4651
        if (!empty($sessionId)) {
4652
            $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4653
        }
4654
4655
        $course = $courseRepo->find($courseId);
4656
4657
        if (0 != $courseId) {
4658
            // Subscribed groups to a LP
4659
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4660
                    TOOL_LEARNPATH_CATEGORY,
4661
                    $category->getId(),
4662
                    $course,
4663
                    $session
4664
                );
4665
        }
4666
4667
        if (!empty($subscribedGroupsInLp)) {
4668
            $noGroupSubscribed = false;
4669
            if (!empty($groups)) {
4670
                $groups = array_column($groups, 'iid');
4671
                /** @var CItemProperty $item */
4672
                foreach ($subscribedGroupsInLp as $item) {
4673
                    if ($item->getGroup() &&
4674
                        in_array($item->getGroup()->getId(), $groups)
4675
                    ) {
4676
                        return true;
4677
                    }
4678
                }
4679
            }
4680
        }
4681
        $response = $noGroupSubscribed && $noUserSubscribed;
4682
4683
        return $response;
4684
    }
4685
4686
    /**
4687
     * Check if a learnpath category is published as course tool.
4688
     *
4689
     * @param int $courseId
4690
     *
4691
     * @return bool
4692
     */
4693
    public static function categoryIsPublished(CLpCategory $category, $courseId)
4694
    {
4695
        return false;
4696
        $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...
4697
        $em = Database::getManager();
4698
4699
        $tools = $em
4700
            ->createQuery("
4701
                SELECT t FROM ChamiloCourseBundle:CTool t
4702
                WHERE t.course = :course AND
4703
                    t.name = :name AND
4704
                    t.image LIKE 'lp_category.%' AND
4705
                    t.link LIKE :link
4706
            ")
4707
            ->setParameters([
4708
                'course' => $courseId,
4709
                'name' => strip_tags($category->getName()),
4710
                'link' => "$link%",
4711
            ])
4712
            ->getResult();
4713
4714
        /** @var CTool $tool */
4715
        $tool = current($tools);
4716
4717
        return $tool ? $tool->getVisibility() : false;
4718
    }
4719
4720
    /**
4721
     * Restart the whole learnpath. Return the URL of the first element.
4722
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4723
     * To use a similar method  statically, use the create_new_attempt() method.
4724
     *
4725
     * @return bool
4726
     */
4727
    public function restart()
4728
    {
4729
        if ($this->debug > 0) {
4730
            error_log('In learnpath::restart()', 0);
4731
        }
4732
        // TODO
4733
        // Call autosave method to save the current progress.
4734
        //$this->index = 0;
4735
        if (api_is_invitee()) {
4736
            return false;
4737
        }
4738
        $session_id = api_get_session_id();
4739
        $course_id = api_get_course_int_id();
4740
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4741
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4742
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4743
        if ($this->debug > 2) {
4744
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4745
        }
4746
        Database::query($sql);
4747
        $view_id = Database::insert_id();
4748
4749
        if ($view_id) {
4750
            $this->lp_view_id = $view_id;
4751
            $this->attempt = $this->attempt + 1;
4752
        } else {
4753
            $this->error = 'Could not insert into item_view table...';
4754
4755
            return false;
4756
        }
4757
        $this->autocomplete_parents($this->current);
4758
        foreach ($this->items as $index => $dummy) {
4759
            $this->items[$index]->restart();
4760
            $this->items[$index]->set_lp_view($this->lp_view_id);
4761
        }
4762
        $this->first();
4763
4764
        return true;
4765
    }
4766
4767
    /**
4768
     * Saves the current item.
4769
     *
4770
     * @return bool
4771
     */
4772
    public function save_current()
4773
    {
4774
        $debug = $this->debug;
4775
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4776
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4777
        if ($debug) {
4778
            error_log('save_current() saving item '.$this->current, 0);
4779
            error_log(''.print_r($this->items, true), 0);
4780
        }
4781
        if (isset($this->items[$this->current]) &&
4782
            is_object($this->items[$this->current])
4783
        ) {
4784
            if ($debug) {
4785
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4786
            }
4787
4788
            $res = $this->items[$this->current]->save(
4789
                false,
4790
                $this->prerequisites_match($this->current)
4791
            );
4792
            $this->autocomplete_parents($this->current);
4793
            $status = $this->items[$this->current]->get_status();
4794
            $this->update_queue[$this->current] = $status;
4795
4796
            if ($debug) {
4797
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4798
            }
4799
4800
            return $res;
4801
        }
4802
4803
        return false;
4804
    }
4805
4806
    /**
4807
     * Saves the given item.
4808
     *
4809
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4810
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4811
     *
4812
     * @return bool
4813
     */
4814
    public function save_item($item_id = null, $from_outside = true)
4815
    {
4816
        $debug = $this->debug;
4817
        if ($debug) {
4818
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4819
        }
4820
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4821
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4822
        if (empty($item_id)) {
4823
            $item_id = (int) $_REQUEST['id'];
4824
        }
4825
4826
        if (empty($item_id)) {
4827
            $item_id = $this->get_current_item_id();
4828
        }
4829
        if (isset($this->items[$item_id]) &&
4830
            is_object($this->items[$item_id])
4831
        ) {
4832
            if ($debug) {
4833
                error_log('Object exists');
4834
            }
4835
4836
            // Saving the item.
4837
            $res = $this->items[$item_id]->save(
4838
                $from_outside,
4839
                $this->prerequisites_match($item_id)
4840
            );
4841
4842
            if ($debug) {
4843
                error_log('update_queue before:');
4844
                error_log(print_r($this->update_queue, 1));
4845
            }
4846
            $this->autocomplete_parents($item_id);
4847
4848
            $status = $this->items[$item_id]->get_status();
4849
            $this->update_queue[$item_id] = $status;
4850
4851
            if ($debug) {
4852
                error_log('get_status(): '.$status);
4853
                error_log('update_queue after:');
4854
                error_log(print_r($this->update_queue, 1));
4855
            }
4856
4857
            return $res;
4858
        }
4859
4860
        return false;
4861
    }
4862
4863
    /**
4864
     * Saves the last item seen's ID only in case.
4865
     */
4866
    public function save_last()
4867
    {
4868
        $course_id = api_get_course_int_id();
4869
        $debug = $this->debug;
4870
        if ($debug) {
4871
            error_log('In learnpath::save_last()', 0);
4872
        }
4873
        $session_condition = api_get_session_condition(
4874
            api_get_session_id(),
4875
            true,
4876
            false
4877
        );
4878
        $table = Database::get_course_table(TABLE_LP_VIEW);
4879
4880
        $userId = $this->get_user_id();
4881
        if (empty($userId)) {
4882
            $userId = api_get_user_id();
4883
            if ($debug) {
4884
                error_log('$this->get_user_id() was empty, used api_get_user_id() instead in '.__FILE__.' line '.__LINE__);
4885
            }
4886
        }
4887
        if (isset($this->current) && !api_is_invitee()) {
4888
            if ($debug) {
4889
                error_log('Saving current item ('.$this->current.') for later review', 0);
4890
            }
4891
            $sql = "UPDATE $table SET
4892
                        last_item = ".$this->get_current_item_id()."
4893
                    WHERE
4894
                        c_id = $course_id AND
4895
                        lp_id = ".$this->get_id()." AND
4896
                        user_id = ".$userId." ".$session_condition;
4897
4898
            if ($debug) {
4899
                error_log('Saving last item seen : '.$sql, 0);
4900
            }
4901
            Database::query($sql);
4902
        }
4903
4904
        if (!api_is_invitee()) {
4905
            // Save progress.
4906
            list($progress) = $this->get_progress_bar_text('%');
4907
            $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
4908
            $scoreAsProgress = $this->getUseScoreAsProgress();
4909
            if ($scoreAsProgress && $scoreAsProgressSetting && (null === $score || empty($score) || -1 == $score)) {
4910
                if ($debug) {
4911
                    error_log("Return false: Dont save score: $score");
4912
                    error_log("progress: $progress");
4913
                }
4914
4915
                return false;
4916
            }
4917
4918
            if ($scoreAsProgress && $scoreAsProgressSetting) {
4919
                $storedProgress = self::getProgress(
4920
                    $this->get_id(),
4921
                    $userId,
4922
                    $course_id,
4923
                    $this->get_lp_session_id()
4924
                );
4925
4926
                // Check if the stored progress is higher than the new value
4927
                if ($storedProgress >= $progress) {
4928
                    if ($debug) {
4929
                        error_log("Return false: New progress value is lower than stored value - Current value: $storedProgress - New value: $progress [lp ".$this->get_id()." - user ".$userId."]");
4930
                    }
4931
4932
                    return false;
4933
                }
4934
            }
4935
            if ($progress >= 0 && $progress <= 100) {
4936
                $progress = (int) $progress;
4937
                $sql = "UPDATE $table SET
4938
                            progress = $progress
4939
                        WHERE
4940
                            c_id = $course_id AND
4941
                            lp_id = ".$this->get_id()." AND
4942
                            user_id = ".$userId." ".$session_condition;
4943
                // Ignore errors as some tables might not have the progress field just yet.
4944
                Database::query($sql);
4945
                $this->progress_db = $progress;
4946
            }
4947
        }
4948
    }
4949
4950
    /**
4951
     * Sets the current item ID (checks if valid and authorized first).
4952
     *
4953
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
4954
     */
4955
    public function set_current_item($item_id = null)
4956
    {
4957
        $debug = $this->debug;
4958
        if ($debug) {
4959
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
4960
        }
4961
        if (empty($item_id)) {
4962
            if ($debug) {
4963
                error_log('No new current item given, ignore...', 0);
4964
            }
4965
            // Do nothing.
4966
        } else {
4967
            if ($debug) {
4968
                error_log('New current item given is '.$item_id.'...', 0);
4969
            }
4970
            if (is_numeric($item_id)) {
4971
                $item_id = (int) $item_id;
4972
                // TODO: Check in database here.
4973
                $this->last = $this->current;
4974
                $this->current = $item_id;
4975
                // TODO: Update $this->index as well.
4976
                foreach ($this->ordered_items as $index => $item) {
4977
                    if ($item == $this->current) {
4978
                        $this->index = $index;
4979
                        break;
4980
                    }
4981
                }
4982
                if ($debug) {
4983
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
4984
                }
4985
            } else {
4986
                if ($debug) {
4987
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
4988
                }
4989
            }
4990
        }
4991
    }
4992
4993
    /**
4994
     * Sets the encoding.
4995
     *
4996
     * @param string $enc New encoding
4997
     *
4998
     * @return bool
4999
     *
5000
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
5001
     */
5002
    public function set_encoding($enc = 'UTF-8')
5003
    {
5004
        $enc = api_refine_encoding_id($enc);
5005
        if (empty($enc)) {
5006
            $enc = api_get_system_encoding();
5007
        }
5008
        if (api_is_encoding_supported($enc)) {
5009
            $lp = $this->get_id();
5010
            if (0 != $lp) {
5011
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5012
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
5013
                        WHERE iid = ".$lp;
5014
                $res = Database::query($sql);
5015
5016
                return $res;
5017
            }
5018
        }
5019
5020
        return false;
5021
    }
5022
5023
    /**
5024
     * Sets the JS lib setting in the database directly.
5025
     * This is the JavaScript library file this lp needs to load on startup.
5026
     *
5027
     * @param string $lib Proximity setting
5028
     *
5029
     * @return bool True on update success. False otherwise.
5030
     */
5031
    public function set_jslib($lib = '')
5032
    {
5033
        $lp = $this->get_id();
5034
5035
        if (0 != $lp) {
5036
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5037
            $lib = Database::escape_string($lib);
5038
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
5039
                    WHERE iid = $lp";
5040
            $res = Database::query($sql);
5041
5042
            return $res;
5043
        }
5044
5045
        return false;
5046
    }
5047
5048
    /**
5049
     * Sets the name of the LP maker (publisher) (and save).
5050
     *
5051
     * @param string $name Optional string giving the new content_maker of this learnpath
5052
     *
5053
     * @return bool True
5054
     */
5055
    public function set_maker($name = '')
5056
    {
5057
        if (empty($name)) {
5058
            return false;
5059
        }
5060
        $this->maker = $name;
5061
        $table = Database::get_course_table(TABLE_LP_MAIN);
5062
        $lp_id = $this->get_id();
5063
        $sql = "UPDATE $table SET
5064
                content_maker = '".Database::escape_string($this->maker)."'
5065
                WHERE iid = $lp_id";
5066
        Database::query($sql);
5067
5068
        return true;
5069
    }
5070
5071
    /**
5072
     * Sets the name of the current learnpath (and save).
5073
     *
5074
     * @param string $name Optional string giving the new name of this learnpath
5075
     *
5076
     * @return bool True/False
5077
     */
5078
    public function set_name($name = null)
5079
    {
5080
        if (empty($name)) {
5081
            return false;
5082
        }
5083
        $this->name = $name;
5084
5085
        $lp_id = $this->get_id();
5086
5087
        $repo = Container::getLpRepository();
5088
        /** @var CLp $lp */
5089
        $lp = $repo->find($lp_id);
5090
        $lp->setName($name);
5091
        $repo->updateNodeForResource($lp);
5092
5093
        /*
5094
        $course_id = $this->course_info['real_id'];
5095
        $sql = "UPDATE $lp_table SET
5096
            name = '$name'
5097
            WHERE iid = $lp_id";
5098
        $result = Database::query($sql);
5099
        // If the lp is visible on the homepage, change his name there.
5100
        if (Database::affected_rows($result)) {
5101
        $session_id = api_get_session_id();
5102
        $session_condition = api_get_session_condition($session_id);
5103
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
5104
        $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
5105
        $sql = "UPDATE $tbl_tool SET name = '$name'
5106
        	    WHERE
5107
        	        c_id = $course_id AND
5108
        	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
5109
        Database::query($sql);*/
5110
5111
        //return true;
5112
        //}
5113
5114
        return false;
5115
    }
5116
5117
    /**
5118
     * Set index specified prefix terms for all items in this path.
5119
     *
5120
     * @param string $terms_string Comma-separated list of terms
5121
     * @param string $prefix       Xapian term prefix
5122
     *
5123
     * @return bool False on error, true otherwise
5124
     */
5125
    public function set_terms_by_prefix($terms_string, $prefix)
5126
    {
5127
        $course_id = api_get_course_int_id();
5128
        if ('true' !== api_get_setting('search_enabled')) {
5129
            return false;
5130
        }
5131
5132
        if (!extension_loaded('xapian')) {
5133
            return false;
5134
        }
5135
5136
        $terms_string = trim($terms_string);
5137
        $terms = explode(',', $terms_string);
5138
        array_walk($terms, 'trim_value');
5139
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5140
5141
        // Don't do anything if no change, verify only at DB, not the search engine.
5142
        if ((0 == count(array_diff($terms, $stored_terms))) && (0 == count(array_diff($stored_terms, $terms)))) {
5143
            return false;
5144
        }
5145
5146
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5147
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5148
5149
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5150
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5151
        $lp_id = (int) $_POST['lp_id'];
5152
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5153
        $result = Database::query($sql);
5154
        $di = new ChamiloIndexer();
5155
5156
        while ($lp_item = Database::fetch_array($result)) {
5157
            // Get search_did.
5158
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5159
            $sql = 'SELECT * FROM %s
5160
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5161
                    LIMIT 1';
5162
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5163
5164
            //echo $sql; echo '<br>';
5165
            $res = Database::query($sql);
5166
            if (Database::num_rows($res) > 0) {
5167
                $se_ref = Database::fetch_array($res);
5168
                // Compare terms.
5169
                $doc = $di->get_document($se_ref['search_did']);
5170
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5171
                $xterms = [];
5172
                foreach ($xapian_terms as $xapian_term) {
5173
                    $xterms[] = substr($xapian_term['name'], 1);
5174
                }
5175
5176
                $dterms = $terms;
5177
                $missing_terms = array_diff($dterms, $xterms);
5178
                $deprecated_terms = array_diff($xterms, $dterms);
5179
5180
                // Save it to search engine.
5181
                foreach ($missing_terms as $term) {
5182
                    $doc->add_term($prefix.$term, 1);
5183
                }
5184
                foreach ($deprecated_terms as $term) {
5185
                    $doc->remove_term($prefix.$term);
5186
                }
5187
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5188
                $di->getDb()->flush();
5189
            }
5190
        }
5191
5192
        return true;
5193
    }
5194
5195
    /**
5196
     * Sets the theme of the LP (local/remote) (and save).
5197
     *
5198
     * @param string $name Optional string giving the new theme of this learnpath
5199
     *
5200
     * @return bool Returns true if theme name is not empty
5201
     */
5202
    public function set_theme($name = '')
5203
    {
5204
        $this->theme = $name;
5205
        $table = Database::get_course_table(TABLE_LP_MAIN);
5206
        $lp_id = $this->get_id();
5207
        $sql = "UPDATE $table
5208
                SET theme = '".Database::escape_string($this->theme)."'
5209
                WHERE iid = $lp_id";
5210
        Database::query($sql);
5211
5212
        return true;
5213
    }
5214
5215
    /**
5216
     * Sets the image of an LP (and save).
5217
     *
5218
     * @param string $name Optional string giving the new image of this learnpath
5219
     *
5220
     * @return bool Returns true if theme name is not empty
5221
     */
5222
    public function set_preview_image($name = '')
5223
    {
5224
        $this->preview_image = $name;
5225
        $table = Database::get_course_table(TABLE_LP_MAIN);
5226
        $lp_id = $this->get_id();
5227
        $sql = "UPDATE $table SET
5228
                preview_image = '".Database::escape_string($this->preview_image)."'
5229
                WHERE iid = $lp_id";
5230
        Database::query($sql);
5231
5232
        return true;
5233
    }
5234
5235
    /**
5236
     * Sets the author of a LP (and save).
5237
     *
5238
     * @param string $name Optional string giving the new author of this learnpath
5239
     *
5240
     * @return bool Returns true if author's name is not empty
5241
     */
5242
    public function set_author($name = '')
5243
    {
5244
        $this->author = $name;
5245
        $table = Database::get_course_table(TABLE_LP_MAIN);
5246
        $lp_id = $this->get_id();
5247
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5248
                WHERE iid = $lp_id";
5249
        Database::query($sql);
5250
5251
        return true;
5252
    }
5253
5254
    /**
5255
     * Sets the hide_toc_frame parameter of a LP (and save).
5256
     *
5257
     * @param int $hide 1 if frame is hidden 0 then else
5258
     *
5259
     * @return bool Returns true if author's name is not empty
5260
     */
5261
    public function set_hide_toc_frame($hide)
5262
    {
5263
        if (intval($hide) == $hide) {
5264
            $this->hide_toc_frame = $hide;
5265
            $table = Database::get_course_table(TABLE_LP_MAIN);
5266
            $lp_id = $this->get_id();
5267
            $sql = "UPDATE $table SET
5268
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5269
                    WHERE iid = $lp_id";
5270
            Database::query($sql);
5271
5272
            return true;
5273
        }
5274
5275
        return false;
5276
    }
5277
5278
    /**
5279
     * Sets the prerequisite of a LP (and save).
5280
     *
5281
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5282
     *
5283
     * @return bool returns true if prerequisite is not empty
5284
     */
5285
    public function set_prerequisite($prerequisite)
5286
    {
5287
        $this->prerequisite = (int) $prerequisite;
5288
        $table = Database::get_course_table(TABLE_LP_MAIN);
5289
        $lp_id = $this->get_id();
5290
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5291
                WHERE iid = $lp_id";
5292
        Database::query($sql);
5293
5294
        return true;
5295
    }
5296
5297
    /**
5298
     * Sets the location/proximity of the LP (local/remote) (and save).
5299
     *
5300
     * @param string $name Optional string giving the new location of this learnpath
5301
     *
5302
     * @return bool True on success / False on error
5303
     */
5304
    public function set_proximity($name = '')
5305
    {
5306
        if (empty($name)) {
5307
            return false;
5308
        }
5309
5310
        $this->proximity = $name;
5311
        $table = Database::get_course_table(TABLE_LP_MAIN);
5312
        $lp_id = $this->get_id();
5313
        $sql = "UPDATE $table SET
5314
                    content_local = '".Database::escape_string($name)."'
5315
                WHERE iid = $lp_id";
5316
        Database::query($sql);
5317
5318
        return true;
5319
    }
5320
5321
    /**
5322
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5323
     *
5324
     * @param int $id DB ID of the item
5325
     */
5326
    public function set_previous_item($id)
5327
    {
5328
        if ($this->debug > 0) {
5329
            error_log('In learnpath::set_previous_item()', 0);
5330
        }
5331
        $this->last = $id;
5332
    }
5333
5334
    /**
5335
     * Sets use_max_score.
5336
     *
5337
     * @param int $use_max_score Optional string giving the new location of this learnpath
5338
     *
5339
     * @return bool True on success / False on error
5340
     */
5341
    public function set_use_max_score($use_max_score = 1)
5342
    {
5343
        $use_max_score = (int) $use_max_score;
5344
        $this->use_max_score = $use_max_score;
5345
        $table = Database::get_course_table(TABLE_LP_MAIN);
5346
        $lp_id = $this->get_id();
5347
        $sql = "UPDATE $table SET
5348
                    use_max_score = '".$this->use_max_score."'
5349
                WHERE iid = $lp_id";
5350
        Database::query($sql);
5351
5352
        return true;
5353
    }
5354
5355
    /**
5356
     * Sets and saves the expired_on date.
5357
     *
5358
     * @param string $expired_on Optional string giving the new author of this learnpath
5359
     *
5360
     * @throws \Doctrine\ORM\OptimisticLockException
5361
     *
5362
     * @return bool Returns true if author's name is not empty
5363
     */
5364
    public function set_expired_on($expired_on)
5365
    {
5366
        $em = Database::getManager();
5367
        /** @var CLp $lp */
5368
        $lp = $em
5369
            ->getRepository('ChamiloCourseBundle:CLp')
5370
            ->findOneBy(
5371
                [
5372
                    'iid' => $this->get_id(),
5373
                ]
5374
            );
5375
5376
        if (!$lp) {
5377
            return false;
5378
        }
5379
5380
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5381
5382
        $lp->setExpiredOn($this->expired_on);
5383
        $em->persist($lp);
5384
        $em->flush();
5385
5386
        return true;
5387
    }
5388
5389
    /**
5390
     * Sets and saves the publicated_on date.
5391
     *
5392
     * @param string $publicated_on Optional string giving the new author of this learnpath
5393
     *
5394
     * @throws \Doctrine\ORM\OptimisticLockException
5395
     *
5396
     * @return bool Returns true if author's name is not empty
5397
     */
5398
    public function set_publicated_on($publicated_on)
5399
    {
5400
        $em = Database::getManager();
5401
        /** @var CLp $lp */
5402
        $lp = $em
5403
            ->getRepository('ChamiloCourseBundle:CLp')
5404
            ->findOneBy(
5405
                [
5406
                    'iid' => $this->get_id(),
5407
                ]
5408
            );
5409
5410
        if (!$lp) {
5411
            return false;
5412
        }
5413
5414
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5415
        $lp->setPublicatedOn($this->publicated_on);
5416
        $em->persist($lp);
5417
        $em->flush();
5418
5419
        return true;
5420
    }
5421
5422
    /**
5423
     * Sets and saves the expired_on date.
5424
     *
5425
     * @return bool Returns true if author's name is not empty
5426
     */
5427
    public function set_modified_on()
5428
    {
5429
        $this->modified_on = api_get_utc_datetime();
5430
        $table = Database::get_course_table(TABLE_LP_MAIN);
5431
        $lp_id = $this->get_id();
5432
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5433
                WHERE iid = $lp_id";
5434
        Database::query($sql);
5435
5436
        return true;
5437
    }
5438
5439
    /**
5440
     * Sets the object's error message.
5441
     *
5442
     * @param string $error Error message. If empty, reinits the error string
5443
     */
5444
    public function set_error_msg($error = '')
5445
    {
5446
        if ($this->debug > 0) {
5447
            error_log('In learnpath::set_error_msg()', 0);
5448
        }
5449
        if (empty($error)) {
5450
            $this->error = '';
5451
        } else {
5452
            $this->error .= $error;
5453
        }
5454
    }
5455
5456
    /**
5457
     * Launches the current item if not 'sco'
5458
     * (starts timer and make sure there is a record ready in the DB).
5459
     *
5460
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5461
     *
5462
     * @return bool
5463
     */
5464
    public function start_current_item($allow_new_attempt = false)
5465
    {
5466
        $debug = $this->debug;
5467
        if ($debug) {
5468
            error_log('In learnpath::start_current_item()');
5469
            error_log('current: '.$this->current);
5470
        }
5471
        if (0 != $this->current && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5472
            $type = $this->get_type();
5473
            $item_type = $this->items[$this->current]->get_type();
5474
            if ((2 == $type && 'sco' != $item_type) ||
5475
                (3 == $type && 'au' != $item_type) ||
5476
                (1 == $type && TOOL_QUIZ != $item_type && TOOL_HOTPOTATOES != $item_type)
5477
            ) {
5478
                if ($debug) {
5479
                    error_log('item type: '.$item_type);
5480
                    error_log('lp type: '.$type);
5481
                }
5482
                $this->items[$this->current]->open($allow_new_attempt);
5483
                $this->autocomplete_parents($this->current);
5484
                $prereq_check = $this->prerequisites_match($this->current);
5485
                if ($debug) {
5486
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5487
                }
5488
                $this->items[$this->current]->save(false, $prereq_check);
5489
            }
5490
            // If sco, then it is supposed to have been updated by some other call.
5491
            if ('sco' == $item_type) {
5492
                $this->items[$this->current]->restart();
5493
            }
5494
        }
5495
        if ($debug) {
5496
            error_log('lp_view_session_id');
5497
            error_log($this->lp_view_session_id);
5498
            error_log('api session id');
5499
            error_log(api_get_session_id());
5500
            error_log('End of learnpath::start_current_item()');
5501
        }
5502
5503
        return true;
5504
    }
5505
5506
    /**
5507
     * Stops the processing and counters for the old item (as held in $this->last).
5508
     *
5509
     * @return bool True/False
5510
     */
5511
    public function stop_previous_item()
5512
    {
5513
        $debug = $this->debug;
5514
        if ($debug) {
5515
            error_log('In learnpath::stop_previous_item()', 0);
5516
        }
5517
5518
        if (0 != $this->last && $this->last != $this->current &&
5519
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5520
        ) {
5521
            if ($debug) {
5522
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5523
            }
5524
            switch ($this->get_type()) {
5525
                case '3':
5526
                    if ('au' != $this->items[$this->last]->get_type()) {
5527
                        if ($debug) {
5528
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5529
                        }
5530
                        $this->items[$this->last]->close();
5531
                    } else {
5532
                        if ($debug) {
5533
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5534
                        }
5535
                    }
5536
                    break;
5537
                case '2':
5538
                    if ('sco' != $this->items[$this->last]->get_type()) {
5539
                        if ($debug) {
5540
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5541
                        }
5542
                        $this->items[$this->last]->close();
5543
                    } else {
5544
                        if ($debug) {
5545
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5546
                        }
5547
                    }
5548
                    break;
5549
                case '1':
5550
                default:
5551
                    if ($debug) {
5552
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5553
                    }
5554
                    $this->items[$this->last]->close();
5555
                    break;
5556
            }
5557
        } else {
5558
            if ($debug) {
5559
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5560
            }
5561
5562
            return false;
5563
        }
5564
5565
        return true;
5566
    }
5567
5568
    /**
5569
     * Updates the default view mode from fullscreen to embedded and inversely.
5570
     *
5571
     * @return string The current default view mode ('fullscreen' or 'embedded')
5572
     */
5573
    public function update_default_view_mode()
5574
    {
5575
        $table = Database::get_course_table(TABLE_LP_MAIN);
5576
        $sql = "SELECT * FROM $table
5577
                WHERE iid = ".$this->get_id();
5578
        $res = Database::query($sql);
5579
        if (Database::num_rows($res) > 0) {
5580
            $row = Database::fetch_array($res);
5581
            $default_view_mode = $row['default_view_mod'];
5582
            $view_mode = $default_view_mode;
5583
            switch ($default_view_mode) {
5584
                case 'fullscreen': // default with popup
5585
                    $view_mode = 'embedded';
5586
                    break;
5587
                case 'embedded': // default view with left menu
5588
                    $view_mode = 'embedframe';
5589
                    break;
5590
                case 'embedframe': //folded menu
5591
                    $view_mode = 'impress';
5592
                    break;
5593
                case 'impress':
5594
                    $view_mode = 'fullscreen';
5595
                    break;
5596
            }
5597
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5598
                    WHERE iid = ".$this->get_id();
5599
            Database::query($sql);
5600
            $this->mode = $view_mode;
5601
5602
            return $view_mode;
5603
        }
5604
5605
        return -1;
5606
    }
5607
5608
    /**
5609
     * Updates the default behaviour about auto-commiting SCORM updates.
5610
     *
5611
     * @return bool True if auto-commit has been set to 'on', false otherwise
5612
     */
5613
    public function update_default_scorm_commit()
5614
    {
5615
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5616
        $sql = "SELECT * FROM $lp_table
5617
                WHERE iid = ".$this->get_id();
5618
        $res = Database::query($sql);
5619
        if (Database::num_rows($res) > 0) {
5620
            $row = Database::fetch_array($res);
5621
            $force = $row['force_commit'];
5622
            if (1 == $force) {
5623
                $force = 0;
5624
                $force_return = false;
5625
            } elseif (0 == $force) {
5626
                $force = 1;
5627
                $force_return = true;
5628
            }
5629
            $sql = "UPDATE $lp_table SET force_commit = $force
5630
                    WHERE iid = ".$this->get_id();
5631
            Database::query($sql);
5632
            $this->force_commit = $force_return;
5633
5634
            return $force_return;
5635
        }
5636
5637
        return -1;
5638
    }
5639
5640
    /**
5641
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5642
     *
5643
     * @return bool True on success, false on failure
5644
     */
5645
    public function update_display_order()
5646
    {
5647
        $course_id = api_get_course_int_id();
5648
        $table = Database::get_course_table(TABLE_LP_MAIN);
5649
        $sql = "SELECT * FROM $table
5650
                WHERE c_id = $course_id
5651
                ORDER BY display_order";
5652
        $res = Database::query($sql);
5653
        if (false === $res) {
5654
            return false;
5655
        }
5656
5657
        $num = Database::num_rows($res);
5658
        // First check the order is correct, globally (might be wrong because
5659
        // of versions < 1.8.4).
5660
        if ($num > 0) {
5661
            $i = 1;
5662
            while ($row = Database::fetch_array($res)) {
5663
                if ($row['display_order'] != $i) {
5664
                    // If we find a gap in the order, we need to fix it.
5665
                    $sql = "UPDATE $table SET display_order = $i
5666
                            WHERE iid = ".$row['iid'];
5667
                    Database::query($sql);
5668
                }
5669
                $i++;
5670
            }
5671
        }
5672
5673
        return true;
5674
    }
5675
5676
    /**
5677
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5678
     *
5679
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5680
     */
5681
    public function update_reinit()
5682
    {
5683
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5684
        $sql = "SELECT * FROM $lp_table
5685
                WHERE iid = ".$this->get_id();
5686
        $res = Database::query($sql);
5687
        if (Database::num_rows($res) > 0) {
5688
            $row = Database::fetch_array($res);
5689
            $force = $row['prevent_reinit'];
5690
            if (1 == $force) {
5691
                $force = 0;
5692
            } elseif (0 == $force) {
5693
                $force = 1;
5694
            }
5695
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5696
                    WHERE iid = ".$this->get_id();
5697
            Database::query($sql);
5698
            $this->prevent_reinit = $force;
5699
5700
            return $force;
5701
        }
5702
5703
        return -1;
5704
    }
5705
5706
    /**
5707
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5708
     *
5709
     * @return string 'single', 'multi' or 'seriousgame'
5710
     *
5711
     * @author ndiechburg <[email protected]>
5712
     */
5713
    public function get_attempt_mode()
5714
    {
5715
        //Set default value for seriousgame_mode
5716
        if (!isset($this->seriousgame_mode)) {
5717
            $this->seriousgame_mode = 0;
5718
        }
5719
        // Set default value for prevent_reinit
5720
        if (!isset($this->prevent_reinit)) {
5721
            $this->prevent_reinit = 1;
5722
        }
5723
        if (1 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
5724
            return 'seriousgame';
5725
        }
5726
        if (0 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
5727
            return 'single';
5728
        }
5729
        if (0 == $this->seriousgame_mode && 0 == $this->prevent_reinit) {
5730
            return 'multiple';
5731
        }
5732
5733
        return 'single';
5734
    }
5735
5736
    /**
5737
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5738
     *
5739
     * @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...
5740
     *
5741
     * @return bool
5742
     *
5743
     * @author ndiechburg <[email protected]>
5744
     */
5745
    public function set_attempt_mode($mode)
5746
    {
5747
        switch ($mode) {
5748
            case 'seriousgame':
5749
                $sg_mode = 1;
5750
                $prevent_reinit = 1;
5751
                break;
5752
            case 'single':
5753
                $sg_mode = 0;
5754
                $prevent_reinit = 1;
5755
                break;
5756
            case 'multiple':
5757
                $sg_mode = 0;
5758
                $prevent_reinit = 0;
5759
                break;
5760
            default:
5761
                $sg_mode = 0;
5762
                $prevent_reinit = 0;
5763
                break;
5764
        }
5765
        $this->prevent_reinit = $prevent_reinit;
5766
        $this->seriousgame_mode = $sg_mode;
5767
        $table = Database::get_course_table(TABLE_LP_MAIN);
5768
        $sql = "UPDATE $table SET
5769
                prevent_reinit = $prevent_reinit ,
5770
                seriousgame_mode = $sg_mode
5771
                WHERE iid = ".$this->get_id();
5772
        $res = Database::query($sql);
5773
        if ($res) {
5774
            return true;
5775
        } else {
5776
            return false;
5777
        }
5778
    }
5779
5780
    /**
5781
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5782
     *
5783
     * @author ndiechburg <[email protected]>
5784
     */
5785
    public function switch_attempt_mode()
5786
    {
5787
        $mode = $this->get_attempt_mode();
5788
        switch ($mode) {
5789
            case 'single':
5790
                $next_mode = 'multiple';
5791
                break;
5792
            case 'multiple':
5793
                $next_mode = 'seriousgame';
5794
                break;
5795
            case 'seriousgame':
5796
            default:
5797
                $next_mode = 'single';
5798
                break;
5799
        }
5800
        $this->set_attempt_mode($next_mode);
5801
    }
5802
5803
    /**
5804
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5805
     * but possibility to do again a completed item.
5806
     *
5807
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5808
     *
5809
     * @author ndiechburg <[email protected]>
5810
     */
5811
    public function set_seriousgame_mode()
5812
    {
5813
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5814
        $sql = "SELECT * FROM $lp_table
5815
                WHERE iid = ".$this->get_id();
5816
        $res = Database::query($sql);
5817
        if (Database::num_rows($res) > 0) {
5818
            $row = Database::fetch_array($res);
5819
            $force = $row['seriousgame_mode'];
5820
            if (1 == $force) {
5821
                $force = 0;
5822
            } elseif (0 == $force) {
5823
                $force = 1;
5824
            }
5825
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5826
			        WHERE iid = ".$this->get_id();
5827
            Database::query($sql);
5828
            $this->seriousgame_mode = $force;
5829
5830
            return $force;
5831
        }
5832
5833
        return -1;
5834
    }
5835
5836
    /**
5837
     * Updates the "scorm_debug" value that shows or hide the debug window.
5838
     *
5839
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5840
     */
5841
    public function update_scorm_debug()
5842
    {
5843
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5844
        $sql = "SELECT * FROM $lp_table
5845
                WHERE iid = ".$this->get_id();
5846
        $res = Database::query($sql);
5847
        if (Database::num_rows($res) > 0) {
5848
            $row = Database::fetch_array($res);
5849
            $force = $row['debug'];
5850
            if (1 == $force) {
5851
                $force = 0;
5852
            } elseif (0 == $force) {
5853
                $force = 1;
5854
            }
5855
            $sql = "UPDATE $lp_table SET debug = $force
5856
                    WHERE iid = ".$this->get_id();
5857
            Database::query($sql);
5858
            $this->scorm_debug = $force;
5859
5860
            return $force;
5861
        }
5862
5863
        return -1;
5864
    }
5865
5866
    /**
5867
     * Function that makes a call to the function sort_tree_array and create_tree_array.
5868
     *
5869
     * @author Kevin Van Den Haute
5870
     *
5871
     * @param  array
5872
     */
5873
    public function tree_array($array)
5874
    {
5875
        $array = $this->sort_tree_array($array);
5876
        $this->create_tree_array($array);
5877
    }
5878
5879
    /**
5880
     * Creates an array with the elements of the learning path tree in it.
5881
     *
5882
     * @author Kevin Van Den Haute
5883
     *
5884
     * @param array $array
5885
     * @param int   $parent
5886
     * @param int   $depth
5887
     * @param array $tmp
5888
     */
5889
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
5890
    {
5891
        if (is_array($array)) {
5892
            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...
5893
                if ($array[$i]['parent_item_id'] == $parent) {
5894
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
5895
                        $tmp[] = $array[$i]['parent_item_id'];
5896
                        $depth++;
5897
                    }
5898
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
5899
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
5900
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
5901
5902
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
5903
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
5904
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
5905
                    $this->arrMenu[] = [
5906
                        'id' => $array[$i]['id'],
5907
                        'ref' => $ref,
5908
                        'item_type' => $array[$i]['item_type'],
5909
                        'title' => $array[$i]['title'],
5910
                        'title_raw' => $array[$i]['title_raw'],
5911
                        'path' => $path,
5912
                        'description' => $array[$i]['description'],
5913
                        'parent_item_id' => $array[$i]['parent_item_id'],
5914
                        'previous_item_id' => $array[$i]['previous_item_id'],
5915
                        'next_item_id' => $array[$i]['next_item_id'],
5916
                        'min_score' => $array[$i]['min_score'],
5917
                        'max_score' => $array[$i]['max_score'],
5918
                        'mastery_score' => $array[$i]['mastery_score'],
5919
                        'display_order' => $array[$i]['display_order'],
5920
                        'prerequisite' => $preq,
5921
                        'depth' => $depth,
5922
                        'audio' => $audio,
5923
                        'prerequisite_min_score' => $prerequisiteMinScore,
5924
                        'prerequisite_max_score' => $prerequisiteMaxScore,
5925
                    ];
5926
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
5927
                }
5928
            }
5929
        }
5930
    }
5931
5932
    /**
5933
     * Sorts a multi dimensional array by parent id and display order.
5934
     *
5935
     * @author Kevin Van Den Haute
5936
     *
5937
     * @param array $array (array with al the learning path items in it)
5938
     *
5939
     * @return array
5940
     */
5941
    public function sort_tree_array($array)
5942
    {
5943
        foreach ($array as $key => $row) {
5944
            $parent[$key] = $row['parent_item_id'];
5945
            $position[$key] = $row['display_order'];
5946
        }
5947
5948
        if (count($array) > 0) {
5949
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
5950
        }
5951
5952
        return $array;
5953
    }
5954
5955
    /**
5956
     * Function that creates a html list of learning path items so that we can add audio files to them.
5957
     *
5958
     * @author Kevin Van Den Haute
5959
     *
5960
     * @return string
5961
     */
5962
    public function overview()
5963
    {
5964
        $return = '';
5965
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
5966
5967
        // we need to start a form when we want to update all the mp3 files
5968
        if ('true' == $update_audio) {
5969
            $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">';
5970
        }
5971
        $return .= '<div id="message"></div>';
5972
        if (0 == count($this->items)) {
5973
            $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');
5974
        } else {
5975
            $return_audio = '<table class="table table-hover table-striped data_table">';
5976
            $return_audio .= '<tr>';
5977
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
5978
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
5979
            $return_audio .= '</tr>';
5980
5981
            if ('true' != $update_audio) {
5982
                $return .= '<div class="col-md-12">';
5983
                $return .= self::return_new_tree($update_audio);
5984
                $return .= '</div>';
5985
                $return .= Display::div(
5986
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
5987
                    ['style' => 'float:left; margin-top:15px;width:100%']
5988
                );
5989
            } else {
5990
                $return_audio .= self::return_new_tree($update_audio);
5991
                $return .= $return_audio.'</table>';
5992
            }
5993
5994
            // We need to close the form when we are updating the mp3 files.
5995
            if ('true' == $update_audio) {
5996
                $return .= '<div class="footer-audio">';
5997
                $return .= Display::button(
5998
                    'save_audio',
5999
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('Save audio and organization'),
6000
                    ['class' => 'btn btn-primary', 'type' => 'submit']
6001
                );
6002
                $return .= '</div>';
6003
            }
6004
        }
6005
6006
        // We need to close the form when we are updating the mp3 files.
6007
        if ('true' == $update_audio && isset($this->arrMenu) && 0 != count($this->arrMenu)) {
6008
            $return .= '</form>';
6009
        }
6010
6011
        return $return;
6012
    }
6013
6014
    /**
6015
     * @param string $update_audio
6016
     *
6017
     * @return array
6018
     */
6019
    public function processBuildMenuElements($update_audio = 'false')
6020
    {
6021
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
6022
        $arrLP = $this->getItemsForForm();
6023
6024
        $this->tree_array($arrLP);
6025
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
6026
        unset($this->arrMenu);
6027
        $default_data = null;
6028
        $default_content = null;
6029
        $elements = [];
6030
        $return_audio = null;
6031
        $iconPath = api_get_path(SYS_PUBLIC_PATH).'img/';
6032
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
6033
        $countItems = count($arrLP);
6034
6035
        $upIcon = Display::return_icon(
6036
            'up.png',
6037
            get_lang('Up'),
6038
            [],
6039
            ICON_SIZE_TINY
6040
        );
6041
6042
        $disableUpIcon = Display::return_icon(
6043
            'up_na.png',
6044
            get_lang('Up'),
6045
            [],
6046
            ICON_SIZE_TINY
6047
        );
6048
6049
        $downIcon = Display::return_icon(
6050
            'down.png',
6051
            get_lang('Down'),
6052
            [],
6053
            ICON_SIZE_TINY
6054
        );
6055
6056
        $disableDownIcon = Display::return_icon(
6057
            'down_na.png',
6058
            get_lang('Down'),
6059
            [],
6060
            ICON_SIZE_TINY
6061
        );
6062
6063
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
6064
6065
        $pluginCalendar = 'true' === api_get_plugin_setting('learning_calendar', 'enabled');
6066
        $plugin = null;
6067
        if ($pluginCalendar) {
6068
            $plugin = LearningCalendarPlugin::create();
6069
        }
6070
6071
        for ($i = 0; $i < $countItems; $i++) {
6072
            $parent_id = $arrLP[$i]['parent_item_id'];
6073
            $title = $arrLP[$i]['title'];
6074
            $title_cut = $arrLP[$i]['title_raw'];
6075
            if (false === $show) {
6076
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
6077
            }
6078
            // Link for the documents
6079
            if ('document' === $arrLP[$i]['item_type'] || TOOL_READOUT_TEXT === $arrLP[$i]['item_type']) {
6080
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6081
                $title_cut = Display::url(
6082
                    $title_cut,
6083
                    $url,
6084
                    [
6085
                        'class' => 'ajax moved',
6086
                        'data-title' => $title,
6087
                        'title' => $title,
6088
                    ]
6089
                );
6090
            }
6091
6092
            // Detect if type is FINAL_ITEM to set path_id to SESSION
6093
            if (TOOL_LP_FINAL_ITEM === $arrLP[$i]['item_type']) {
6094
                Session::write('pathItem', $arrLP[$i]['path']);
6095
            }
6096
6097
            $oddClass = 'row_even';
6098
            if (0 == ($i % 2)) {
6099
                $oddClass = 'row_odd';
6100
            }
6101
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
6102
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
6103
6104
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
6105
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
6106
            } else {
6107
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
6108
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
6109
                } else {
6110
                    if (TOOL_LP_FINAL_ITEM === $arrLP[$i]['item_type']) {
6111
                        $icon = Display::return_icon('certificate.png');
6112
                    } else {
6113
                        $icon = Display::return_icon('folder_document.png');
6114
                    }
6115
                }
6116
            }
6117
6118
            // The audio column.
6119
            $return_audio .= '<td align="left" style="padding-left:10px;">';
6120
            $audio = '';
6121
            if (!$update_audio || 'true' != $update_audio) {
6122
                if (empty($arrLP[$i]['audio'])) {
6123
                    $audio .= '';
6124
                }
6125
            } else {
6126
                $types = self::getChapterTypes();
6127
                if (!in_array($arrLP[$i]['item_type'], $types)) {
6128
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
6129
                    if (!empty($arrLP[$i]['audio'])) {
6130
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
6131
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('Remove audio');
6132
                    }
6133
                }
6134
            }
6135
6136
            $return_audio .= Display::span($icon.' '.$title).
6137
                Display::tag(
6138
                    'td',
6139
                    $audio,
6140
                    ['style' => '']
6141
                );
6142
            $return_audio .= '</td>';
6143
            $move_icon = '';
6144
            $move_item_icon = '';
6145
            $edit_icon = '';
6146
            $delete_icon = '';
6147
            $audio_icon = '';
6148
            $prerequisities_icon = '';
6149
            $forumIcon = '';
6150
            $previewIcon = '';
6151
            $pluginCalendarIcon = '';
6152
            $orderIcons = '';
6153
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
6154
6155
            if ($is_allowed_to_edit) {
6156
                if (!$update_audio || 'true' != $update_audio) {
6157
                    if (TOOL_LP_FINAL_ITEM !== $arrLP[$i]['item_type']) {
6158
                        $move_icon .= '<a class="moved" href="#">';
6159
                        $move_icon .= Display::return_icon(
6160
                            'move_everywhere.png',
6161
                            get_lang('Move'),
6162
                            [],
6163
                            ICON_SIZE_TINY
6164
                        );
6165
                        $move_icon .= '</a>';
6166
                    }
6167
                }
6168
6169
                // No edit for this item types
6170
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6171
                    if ('dir' != $arrLP[$i]['item_type']) {
6172
                        $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">';
6173
                        $edit_icon .= Display::return_icon(
6174
                            'edit.png',
6175
                            get_lang('Edit section description/name'),
6176
                            [],
6177
                            ICON_SIZE_TINY
6178
                        );
6179
                        $edit_icon .= '</a>';
6180
6181
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6182
                            $forumThread = null;
6183
                            if (isset($this->items[$arrLP[$i]['id']])) {
6184
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6185
                                    $this->course_int_id,
6186
                                    $this->lp_session_id
6187
                                );
6188
                            }
6189
                            if ($forumThread) {
6190
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6191
                                        'action' => 'dissociate_forum',
6192
                                        'id' => $arrLP[$i]['id'],
6193
                                        'lp_id' => $this->lp_id,
6194
                                    ]);
6195
                                $forumIcon = Display::url(
6196
                                    Display::return_icon(
6197
                                        'forum.png',
6198
                                        get_lang('Dissociate the forum of this learning path item'),
6199
                                        [],
6200
                                        ICON_SIZE_TINY
6201
                                    ),
6202
                                    $forumIconUrl,
6203
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6204
                                );
6205
                            } else {
6206
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6207
                                        'action' => 'create_forum',
6208
                                        'id' => $arrLP[$i]['id'],
6209
                                        'lp_id' => $this->lp_id,
6210
                                    ]);
6211
                                $forumIcon = Display::url(
6212
                                    Display::return_icon(
6213
                                        'forum.png',
6214
                                        get_lang('Associate a forum to this learning path item'),
6215
                                        [],
6216
                                        ICON_SIZE_TINY
6217
                                    ),
6218
                                    $forumIconUrl,
6219
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6220
                                );
6221
                            }
6222
                        }
6223
                    } else {
6224
                        $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">';
6225
                        $edit_icon .= Display::return_icon(
6226
                            'edit.png',
6227
                            get_lang('Edit section description/name'),
6228
                            [],
6229
                            ICON_SIZE_TINY
6230
                        );
6231
                        $edit_icon .= '</a>';
6232
                    }
6233
                } else {
6234
                    if (TOOL_LP_FINAL_ITEM == $arrLP[$i]['item_type']) {
6235
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6236
                        $edit_icon .= Display::return_icon(
6237
                            'edit.png',
6238
                            get_lang('Edit'),
6239
                            [],
6240
                            ICON_SIZE_TINY
6241
                        );
6242
                        $edit_icon .= '</a>';
6243
                    }
6244
                }
6245
6246
                if ($pluginCalendar) {
6247
                    $pluginLink = $pluginUrl.
6248
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6249
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('1 day'), [], ICON_SIZE_TINY);
6250
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6251
                    if ($itemInfo && 1 == $itemInfo['value']) {
6252
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('1 day'), [], ICON_SIZE_TINY);
6253
                    }
6254
                    $pluginCalendarIcon = Display::url(
6255
                        $iconCalendar,
6256
                        $pluginLink,
6257
                        ['class' => 'btn btn-default']
6258
                    );
6259
                }
6260
6261
                if ('final_item' != $arrLP[$i]['item_type']) {
6262
                    $orderIcons = Display::url(
6263
                        $upIcon,
6264
                        'javascript:void(0)',
6265
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
6266
                    );
6267
                    $orderIcons .= Display::url(
6268
                        $downIcon,
6269
                        'javascript:void(0)',
6270
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
6271
                    );
6272
                }
6273
6274
                $delete_icon .= ' <a
6275
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
6276
                    onclick="return confirmation(\''.addslashes($title).'\');"
6277
                    class="btn btn-default">';
6278
                $delete_icon .= Display::return_icon(
6279
                    'delete.png',
6280
                    get_lang('Delete section'),
6281
                    [],
6282
                    ICON_SIZE_TINY
6283
                );
6284
                $delete_icon .= '</a>';
6285
6286
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6287
                $previewImage = Display::return_icon(
6288
                    'preview_view.png',
6289
                    get_lang('Preview'),
6290
                    [],
6291
                    ICON_SIZE_TINY
6292
                );
6293
6294
                switch ($arrLP[$i]['item_type']) {
6295
                    case TOOL_DOCUMENT:
6296
                    case TOOL_LP_FINAL_ITEM:
6297
                    case TOOL_READOUT_TEXT:
6298
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6299
                        $previewIcon = Display::url(
6300
                            $previewImage,
6301
                            $urlPreviewLink,
6302
                            [
6303
                                'target' => '_blank',
6304
                                'class' => 'btn btn-default',
6305
                                'data-title' => $arrLP[$i]['title'],
6306
                                'title' => $arrLP[$i]['title'],
6307
                            ]
6308
                        );
6309
                        break;
6310
                    case TOOL_THREAD:
6311
                    case TOOL_FORUM:
6312
                    case TOOL_QUIZ:
6313
                    case TOOL_STUDENTPUBLICATION:
6314
                    case TOOL_LINK:
6315
                        $class = 'btn btn-default';
6316
                        $target = '_blank';
6317
                        $link = self::rl_get_resource_link_for_learnpath(
6318
                            $this->course_int_id,
6319
                            $this->lp_id,
6320
                            $arrLP[$i]['id'],
6321
                            0
6322
                        );
6323
                        $previewIcon = Display::url(
6324
                            $previewImage,
6325
                            $link,
6326
                            [
6327
                                'class' => $class,
6328
                                'data-title' => $arrLP[$i]['title'],
6329
                                'title' => $arrLP[$i]['title'],
6330
                                'target' => $target,
6331
                            ]
6332
                        );
6333
                        break;
6334
                    default:
6335
                        $previewIcon = Display::url(
6336
                            $previewImage,
6337
                            $url.'&action=view_item',
6338
                            ['class' => 'btn btn-default', 'target' => '_blank']
6339
                        );
6340
                        break;
6341
                }
6342
6343
                if ('dir' != $arrLP[$i]['item_type']) {
6344
                    $prerequisities_icon = Display::url(
6345
                        Display::return_icon(
6346
                            'accept.png',
6347
                            get_lang('Prerequisites'),
6348
                            [],
6349
                            ICON_SIZE_TINY
6350
                        ),
6351
                        $url.'&action=edit_item_prereq',
6352
                        ['class' => 'btn btn-default']
6353
                    );
6354
                    if ('final_item' != $arrLP[$i]['item_type']) {
6355
                        /*$move_item_icon = Display::url(
6356
                            Display::return_icon(
6357
                                'move.png',
6358
                                get_lang('Move'),
6359
                                [],
6360
                                ICON_SIZE_TINY
6361
                            ),
6362
                            $url.'&action=move_item',
6363
                            ['class' => 'btn btn-default']
6364
                        );*/
6365
                    }
6366
                    $audio_icon = Display::url(
6367
                        Display::return_icon(
6368
                            'audio.png',
6369
                            get_lang('Upload'),
6370
                            [],
6371
                            ICON_SIZE_TINY
6372
                        ),
6373
                        $url.'&action=add_audio',
6374
                        ['class' => 'btn btn-default']
6375
                    );
6376
                }
6377
            }
6378
            if ('true' != $update_audio) {
6379
                $row = $move_icon.' '.$icon.
6380
                    Display::span($title_cut).
6381
                    Display::tag(
6382
                        'div',
6383
                        "<div class=\"btn-group btn-group-xs\">
6384
                                    $previewIcon
6385
                                    $audio
6386
                                    $edit_icon
6387
                                    $pluginCalendarIcon
6388
                                    $forumIcon
6389
                                    $prerequisities_icon
6390
                                    $move_item_icon
6391
                                    $audio_icon
6392
                                    $orderIcons
6393
                                    $delete_icon
6394
                                </div>",
6395
                        ['class' => 'btn-toolbar button_actions']
6396
                    );
6397
            } else {
6398
                $row =
6399
                    Display::span($title.$icon).
6400
                    Display::span($audio, ['class' => 'button_actions']);
6401
            }
6402
6403
            $default_data[$arrLP[$i]['id']] = $row;
6404
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6405
6406
            if (empty($parent_id)) {
6407
                $elements[$arrLP[$i]['id']]['data'] = $row;
6408
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6409
            } else {
6410
                $parent_arrays = [];
6411
                if ($arrLP[$i]['depth'] > 1) {
6412
                    // Getting list of parents
6413
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6414
                        foreach ($arrLP as $item) {
6415
                            if ($item['id'] == $parent_id) {
6416
                                if (0 == $item['parent_item_id']) {
6417
                                    $parent_id = $item['id'];
6418
                                    break;
6419
                                } else {
6420
                                    $parent_id = $item['parent_item_id'];
6421
                                    if (empty($parent_arrays)) {
6422
                                        $parent_arrays[] = intval($item['id']);
6423
                                    }
6424
                                    $parent_arrays[] = $parent_id;
6425
                                    break;
6426
                                }
6427
                            }
6428
                        }
6429
                    }
6430
                }
6431
6432
                if (!empty($parent_arrays)) {
6433
                    $parent_arrays = array_reverse($parent_arrays);
6434
                    $val = '$elements';
6435
                    $x = 0;
6436
                    foreach ($parent_arrays as $item) {
6437
                        if ($x != count($parent_arrays) - 1) {
6438
                            $val .= '["'.$item.'"]["children"]';
6439
                        } else {
6440
                            $val .= '["'.$item.'"]["children"]';
6441
                        }
6442
                        $x++;
6443
                    }
6444
                    $val .= "";
6445
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6446
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6447
                } else {
6448
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6449
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6450
                }
6451
            }
6452
        }
6453
6454
        return [
6455
            'elements' => $elements,
6456
            'default_data' => $default_data,
6457
            'default_content' => $default_content,
6458
            'return_audio' => $return_audio,
6459
        ];
6460
    }
6461
6462
    /**
6463
     * @param string $updateAudio true/false strings
6464
     *
6465
     * @return string
6466
     */
6467
    public function returnLpItemList($updateAudio)
6468
    {
6469
        $result = $this->processBuildMenuElements($updateAudio);
6470
6471
        $html = self::print_recursive(
6472
            $result['elements'],
6473
            $result['default_data'],
6474
            $result['default_content']
6475
        );
6476
6477
        if (!empty($html)) {
6478
            $html .= Display::return_message(get_lang('Drag and drop an element here'));
6479
        }
6480
6481
        return $html;
6482
    }
6483
6484
    /**
6485
     * @param string $update_audio
6486
     * @param bool   $drop_element_here
6487
     *
6488
     * @return string
6489
     */
6490
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6491
    {
6492
        $result = $this->processBuildMenuElements($update_audio);
6493
6494
        $list = '<ul id="lp_item_list">';
6495
        $tree = $this->print_recursive(
6496
            $result['elements'],
6497
            $result['default_data'],
6498
            $result['default_content']
6499
        );
6500
6501
        if (!empty($tree)) {
6502
            $list .= $tree;
6503
        } else {
6504
            if ($drop_element_here) {
6505
                $list .= Display::return_message(get_lang('Drag and drop an element here'));
6506
            }
6507
        }
6508
        $list .= '</ul>';
6509
6510
        $return = Display::panelCollapse(
6511
            $this->name,
6512
            $list,
6513
            'scorm-list',
6514
            null,
6515
            'scorm-list-accordion',
6516
            'scorm-list-collapse'
6517
        );
6518
6519
        if ('true' === $update_audio) {
6520
            $return = $result['return_audio'];
6521
        }
6522
6523
        return $return;
6524
    }
6525
6526
    /**
6527
     * @param array $elements
6528
     * @param array $default_data
6529
     * @param array $default_content
6530
     *
6531
     * @return string
6532
     */
6533
    public function print_recursive($elements, $default_data, $default_content)
6534
    {
6535
        $return = '';
6536
        foreach ($elements as $key => $item) {
6537
            if (isset($item['load_data']) || empty($item['data'])) {
6538
                $item['data'] = $default_data[$item['load_data']];
6539
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6540
            }
6541
            $sub_list = '';
6542
            if (isset($item['type']) && 'dir' === $item['type']) {
6543
                // empty value
6544
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6545
            }
6546
            if (empty($item['children'])) {
6547
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6548
                $active = null;
6549
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6550
                    $active = 'active';
6551
                }
6552
                $return .= Display::tag(
6553
                    'li',
6554
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6555
                    ['id' => $key, 'class' => 'record li_container']
6556
                );
6557
            } else {
6558
                // Sections
6559
                $data = '';
6560
                if (isset($item['children'])) {
6561
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6562
                }
6563
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6564
                $return .= Display::tag(
6565
                    'li',
6566
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6567
                    ['id' => $key, 'class' => 'record li_container']
6568
                );
6569
            }
6570
        }
6571
6572
        return $return;
6573
    }
6574
6575
    /**
6576
     * This function builds the action menu.
6577
     *
6578
     * @param bool   $returnString           Optional
6579
     * @param bool   $showRequirementButtons Optional. Allow show the requirements button
6580
     * @param bool   $isConfigPage           Optional. If is the config page, show the edit button
6581
     * @param bool   $allowExpand            Optional. Allow show the expand/contract button
6582
     * @param string $action
6583
     * @param array  $extraField
6584
     *
6585
     * @return string
6586
     */
6587
    public function build_action_menu(
6588
        $returnString = false,
6589
        $showRequirementButtons = true,
6590
        $isConfigPage = false,
6591
        $allowExpand = true,
6592
        $action = '',
6593
        $extraField = []
6594
    ) {
6595
        $actionsRight = '';
6596
        $lpId = $this->lp_id;
6597
        if (!isset($extraField['backTo']) && empty($extraField['backTo'])) {
6598
            $back = Display::url(
6599
                Display::return_icon(
6600
                    'back.png',
6601
                    get_lang('Back to learning paths'),
6602
                    '',
6603
                    ICON_SIZE_MEDIUM
6604
                ),
6605
                'lp_controller.php?'.api_get_cidreq()
6606
            );
6607
        } else {
6608
            $back = Display::url(
6609
                Display::return_icon(
6610
                    'back.png',
6611
                    get_lang('Back'),
6612
                    '',
6613
                    ICON_SIZE_MEDIUM
6614
                ),
6615
                $extraField['backTo']
6616
            );
6617
        }
6618
6619
        /*if ($backToBuild) {
6620
            $back = Display::url(
6621
                Display::return_icon(
6622
                    'back.png',
6623
                    get_lang('GoBack'),
6624
                    '',
6625
                    ICON_SIZE_MEDIUM
6626
                ),
6627
                "lp_controller.php?action=add_item&type=step&lp_id=$lpId&".api_get_cidreq()
6628
            );
6629
        }*/
6630
6631
        $actionsLeft = $back;
6632
6633
        $actionsLeft .= Display::url(
6634
            Display::return_icon(
6635
                'preview_view.png',
6636
                get_lang('Preview'),
6637
                '',
6638
                ICON_SIZE_MEDIUM
6639
            ),
6640
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6641
                'action' => 'view',
6642
                'lp_id' => $lpId,
6643
                'isStudentView' => 'true',
6644
            ])
6645
        );
6646
6647
        $actionsLeft .= Display::url(
6648
            Display::return_icon(
6649
                'upload_audio.png',
6650
                get_lang('Add audio'),
6651
                '',
6652
                ICON_SIZE_MEDIUM
6653
            ),
6654
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6655
                'action' => 'admin_view',
6656
                'lp_id' => $lpId,
6657
                'updateaudio' => 'true',
6658
            ])
6659
        );
6660
6661
        $subscriptionSettings = self::getSubscriptionSettings();
6662
6663
        $request = api_request_uri();
6664
        if (false === strpos($request, 'edit')) {
6665
            $actionsLeft .= Display::url(
6666
                Display::return_icon(
6667
                    'settings.png',
6668
                    get_lang('Course settings'),
6669
                    '',
6670
                    ICON_SIZE_MEDIUM
6671
                ),
6672
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6673
                    'action' => 'edit',
6674
                    'lp_id' => $lpId,
6675
                ])
6676
            );
6677
        }
6678
6679
        if ((false === strpos($request, 'build') &&
6680
            false === strpos($request, 'add_item')) ||
6681
            in_array($action, ['add_audio'])
6682
        ) {
6683
            $actionsLeft .= Display::url(
6684
                Display::return_icon(
6685
                    'edit.png',
6686
                    get_lang('Edit'),
6687
                    '',
6688
                    ICON_SIZE_MEDIUM
6689
                ),
6690
                'lp_controller.php?'.http_build_query([
6691
                    'action' => 'build',
6692
                    'lp_id' => $lpId,
6693
                ]).'&'.api_get_cidreq()
6694
            );
6695
        }
6696
6697
        if (false === strpos(api_get_self(), 'lp_subscribe_users.php')) {
6698
            if (1 == $this->subscribeUsers &&
6699
                $subscriptionSettings['allow_add_users_to_lp']) {
6700
                $actionsLeft .= Display::url(
6701
                    Display::return_icon(
6702
                        'user.png',
6703
                        get_lang('Subscribe users to learning path'),
6704
                        '',
6705
                        ICON_SIZE_MEDIUM
6706
                    ),
6707
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=$lpId&".api_get_cidreq()
6708
                );
6709
            }
6710
        }
6711
6712
        if ($allowExpand) {
6713
            $actionsLeft .= Display::url(
6714
                Display::return_icon(
6715
                    'expand.png',
6716
                    get_lang('Expand'),
6717
                    ['id' => 'expand'],
6718
                    ICON_SIZE_MEDIUM
6719
                ).
6720
                Display::return_icon(
6721
                    'contract.png',
6722
                    get_lang('Collapse'),
6723
                    ['id' => 'contract', 'class' => 'hide'],
6724
                    ICON_SIZE_MEDIUM
6725
                ),
6726
                '#',
6727
                ['role' => 'button', 'id' => 'hide_bar_template']
6728
            );
6729
        }
6730
6731
        if ($showRequirementButtons) {
6732
            $buttons = [
6733
                [
6734
                    'title' => get_lang('Set previous step as prerequisite for each step'),
6735
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6736
                        'action' => 'set_previous_step_as_prerequisite',
6737
                        'lp_id' => $lpId,
6738
                    ]),
6739
                ],
6740
                [
6741
                    'title' => get_lang('Clear all prerequisites'),
6742
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6743
                        'action' => 'clear_prerequisites',
6744
                        'lp_id' => $lpId,
6745
                    ]),
6746
                ],
6747
            ];
6748
            $actionsRight = Display::groupButtonWithDropDown(
6749
                get_lang('Prerequisites options'),
6750
                $buttons,
6751
                true
6752
            );
6753
        }
6754
6755
        if (api_is_platform_admin() && isset($extraField['authorlp'])) {
6756
            $actionsLeft .= Display::url(
6757
                Display::return_icon(
6758
                    'add-groups.png',
6759
                    get_lang('Author'),
6760
                    '',
6761
                    ICON_SIZE_MEDIUM
6762
                ),
6763
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6764
                    'action' => 'author_view',
6765
                    'lp_id' => $lpId,
6766
                ])
6767
            );
6768
        }
6769
6770
        $toolbar = Display::toolbarAction(
6771
            'actions-lp-controller',
6772
            [$actionsLeft, $actionsRight]
6773
        );
6774
6775
        if ($returnString) {
6776
            return $toolbar;
6777
        }
6778
6779
        echo $toolbar;
6780
    }
6781
6782
    /**
6783
     * Creates the default learning path folder.
6784
     *
6785
     * @param array $course
6786
     * @param int   $creatorId
6787
     *
6788
     * @return bool
6789
     */
6790
    public static function generate_learning_path_folder($course, $creatorId = 0)
6791
    {
6792
        // Creating learning_path folder
6793
        $dir = 'learning_path';
6794
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6795
        $folder = false;
6796
6797
        $folderData = create_unexisting_directory(
6798
            $course,
6799
            $creatorId,
6800
            0,
6801
            null,
6802
            0,
6803
            '',
6804
            $dir,
6805
            get_lang('Learning paths'),
6806
            0
6807
        );
6808
6809
        if (!empty($folderData)) {
6810
            $folder = true;
6811
        }
6812
6813
        return $folder;
6814
    }
6815
6816
    /**
6817
     * @param array  $course
6818
     * @param string $lp_name
6819
     * @param int    $creatorId
6820
     *
6821
     * @return array
6822
     */
6823
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6824
    {
6825
        $filepath = '';
6826
        $dir = '/learning_path/';
6827
6828
        if (empty($lp_name)) {
6829
            $lp_name = $this->name;
6830
        }
6831
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6832
        $folder = self::generate_learning_path_folder($course, $creatorId);
6833
6834
        // Limits title size
6835
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6836
        $dir = $dir.$title;
6837
6838
        // Creating LP folder
6839
        $documentId = null;
6840
        if ($folder) {
6841
            $folderData = create_unexisting_directory(
6842
                $course,
6843
                $creatorId,
6844
                0,
6845
                0,
6846
                0,
6847
                $filepath,
6848
                $dir,
6849
                $lp_name
6850
            );
6851
            if (!empty($folderData)) {
6852
                $folder = true;
6853
            }
6854
6855
            $documentId = $folderData->getIid();
6856
            $dir = $dir.'/';
6857
            if ($folder) {
6858
                // $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6859
            }
6860
        }
6861
6862
        if (empty($documentId)) {
6863
            $dir = api_remove_trailing_slash($dir);
6864
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6865
        }
6866
6867
        $array = [
6868
            'dir' => $dir,
6869
            'filepath' => $filepath,
6870
            'folder' => $folder,
6871
            'id' => $documentId,
6872
        ];
6873
6874
        return $array;
6875
    }
6876
6877
    /**
6878
     * Create a new document //still needs some finetuning.
6879
     *
6880
     * @param array  $courseInfo
6881
     * @param string $content
6882
     * @param string $title
6883
     * @param string $extension
6884
     * @param int    $parentId
6885
     * @param int    $creatorId  creator id
6886
     *
6887
     * @return int
6888
     */
6889
    public function create_document(
6890
        $courseInfo,
6891
        $content = '',
6892
        $title = '',
6893
        $extension = 'html',
6894
        $parentId = 0,
6895
        $creatorId = 0
6896
    ) {
6897
        if (!empty($courseInfo)) {
6898
            $course_id = $courseInfo['real_id'];
6899
        } else {
6900
            $course_id = api_get_course_int_id();
6901
        }
6902
6903
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6904
        $sessionId = api_get_session_id();
6905
6906
        // Generates folder
6907
        $result = $this->generate_lp_folder($courseInfo);
6908
        $dir = $result['dir'];
6909
6910
        if (empty($parentId) || '/' == $parentId) {
6911
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6912
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6913
6914
            if ('/' === $parentId) {
6915
                $dir = '/';
6916
            }
6917
6918
            // Please, do not modify this dirname formatting.
6919
            if (strstr($dir, '..')) {
6920
                $dir = '/';
6921
            }
6922
6923
            if (!empty($dir[0]) && '.' == $dir[0]) {
6924
                $dir = substr($dir, 1);
6925
            }
6926
            if (!empty($dir[0]) && '/' != $dir[0]) {
6927
                $dir = '/'.$dir;
6928
            }
6929
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
6930
                $dir .= '/';
6931
            }
6932
        } else {
6933
            $parentInfo = DocumentManager::get_document_data_by_id(
6934
                $parentId,
6935
                $courseInfo['code']
6936
            );
6937
            if (!empty($parentInfo)) {
6938
                $dir = $parentInfo['path'].'/';
6939
            }
6940
        }
6941
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6942
        // is already escaped twice when it gets here.
6943
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6944
        if (!empty($title)) {
6945
            $title = api_replace_dangerous_char(stripslashes($title));
6946
        } else {
6947
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6948
        }
6949
6950
        $title = disable_dangerous_file($title);
6951
        $filename = $title;
6952
        $content = !empty($content) ? $content : $_POST['content_lp'];
6953
        $tmp_filename = $filename;
6954
        /*$i = 0;
6955
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
6956
            $tmp_filename = $filename.'_'.++$i;
6957
        }*/
6958
        $filename = $tmp_filename.'.'.$extension;
6959
6960
        if ('html' === $extension) {
6961
            $content = stripslashes($content);
6962
            $content = str_replace(
6963
                api_get_path(WEB_COURSE_PATH),
6964
                api_get_path(REL_PATH).'courses/',
6965
                $content
6966
            );
6967
6968
            // Change the path of mp3 to absolute.
6969
            // The first regexp deals with :// urls.
6970
            $content = preg_replace(
6971
                "|(flashvars=\"file=)([^:/]+)/|",
6972
                "$1".api_get_path(
6973
                    REL_COURSE_PATH
6974
                ).$courseInfo['path'].'/document/',
6975
                $content
6976
            );
6977
            // The second regexp deals with audio/ urls.
6978
            $content = preg_replace(
6979
                "|(flashvars=\"file=)([^/]+)/|",
6980
                "$1".api_get_path(
6981
                    REL_COURSE_PATH
6982
                ).$courseInfo['path'].'/document/$2/',
6983
                $content
6984
            );
6985
            // For flv player: To prevent edition problem with firefox,
6986
            // we have to use a strange tip (don't blame me please).
6987
            $content = str_replace(
6988
                '</body>',
6989
                '<style type="text/css">body{}</style></body>',
6990
                $content
6991
            );
6992
        }
6993
6994
        $save_file_path = $dir.$filename;
6995
6996
        $document = DocumentManager::addDocument(
6997
            $courseInfo,
6998
            $save_file_path,
6999
            'file',
7000
            '',
7001
            $tmp_filename,
7002
            '',
7003
            0, //readonly
7004
            true,
7005
            null,
7006
            $sessionId,
7007
            $creatorId,
7008
            false,
7009
            $content,
7010
            $parentId
7011
        );
7012
7013
        $document_id = $document->getIid();
7014
        if ($document_id) {
7015
            $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7016
            $new_title = $originalTitle;
7017
7018
            if ($new_comment || $new_title) {
7019
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7020
                $ct = '';
7021
                if ($new_comment) {
7022
                    $ct .= ", comment='".Database::escape_string($new_comment)."'";
7023
                }
7024
                if ($new_title) {
7025
                    $ct .= ", title='".Database::escape_string($new_title)."' ";
7026
                }
7027
7028
                $sql = "UPDATE $tbl_doc SET ".substr($ct, 1)."
7029
                        WHERE iid = $document_id ";
7030
                Database::query($sql);
7031
            }
7032
        }
7033
7034
        return $document_id;
7035
    }
7036
7037
    /**
7038
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
7039
     */
7040
    public function edit_document()
7041
    {
7042
        $repo = Container::getDocumentRepository();
7043
        if (isset($_REQUEST['document_id']) && !empty($_REQUEST['document_id'])) {
7044
            $id = (int) $_REQUEST['document_id'];
7045
            /** @var CDocument $document */
7046
            $document = $repo->find($id);
7047
7048
            if ($document->getResourceNode()->hasEditableTextContent()) {
7049
                $repo->updateResourceFileContent($document, $_REQUEST['content_lp']);
7050
            }
7051
7052
            $document->setTitle($_REQUEST['title']);
7053
            $repo->update($document);
7054
        }
7055
    }
7056
7057
    /**
7058
     * Displays the selected item, with a panel for manipulating the item.
7059
     *
7060
     * @param CLpItem $lpItem
7061
     * @param string  $msg
7062
     * @param bool    $show_actions
7063
     *
7064
     * @return string
7065
     */
7066
    public function display_item($lpItem, $msg = null, $show_actions = true)
7067
    {
7068
        $course_id = api_get_course_int_id();
7069
        $return = '';
7070
7071
        if (empty($lpItem)) {
7072
            return '';
7073
        }
7074
        $item_id = $lpItem->getIid();
7075
        $itemType = $lpItem->getItemType();
7076
        $lpId = $lpItem->getLpId();
7077
        $path = $lpItem->getPath();
7078
7079
        Session::write('parent_item_id', 'dir' === $itemType ? $item_id : 0);
7080
7081
        // Prevents wrong parent selection for document, see Bug#1251.
7082
        if ('dir' !== $itemType) {
7083
            Session::write('parent_item_id', $lpItem->getParentItemId());
7084
        }
7085
7086
        if ($show_actions) {
7087
            $return .= $this->displayItemMenu($lpItem);
7088
        }
7089
        $return .= '<div style="padding:10px;">';
7090
7091
        if ('' != $msg) {
7092
            $return .= $msg;
7093
        }
7094
7095
        $return .= '<h3>'.$lpItem->getTitle().'</h3>';
7096
7097
        switch ($itemType) {
7098
            case TOOL_THREAD:
7099
                $link = $this->rl_get_resource_link_for_learnpath(
7100
                    $course_id,
7101
                    $lpId,
7102
                    $item_id,
7103
                    0
7104
                );
7105
                $return .= Display::url(
7106
                    get_lang('Go to thread'),
7107
                    $link,
7108
                    ['class' => 'btn btn-primary']
7109
                );
7110
                break;
7111
            case TOOL_FORUM:
7112
                $return .= Display::url(
7113
                    get_lang('Go to the forum'),
7114
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$path,
7115
                    ['class' => 'btn btn-primary']
7116
                );
7117
                break;
7118
            case TOOL_QUIZ:
7119
                if (!empty($path)) {
7120
                    $exercise = new Exercise();
7121
                    $exercise->read($path);
7122
                    $return .= $exercise->description.'<br />';
7123
                    $return .= Display::url(
7124
                        get_lang('Go to exercise'),
7125
                        api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
7126
                        ['class' => 'btn btn-primary']
7127
                    );
7128
                }
7129
                break;
7130
            case TOOL_LP_FINAL_ITEM:
7131
                $return .= $this->getSavedFinalItem();
7132
                break;
7133
            case TOOL_DOCUMENT:
7134
            case TOOL_READOUT_TEXT:
7135
                $repo = Container::getDocumentRepository();
7136
                /** @var CDocument $document */
7137
                $document = $repo->find($lpItem->getPath());
7138
                $return .= $this->display_document($document, true, true);
7139
                break;
7140
            case TOOL_HOTPOTATOES:
7141
                $return .= $this->display_document($document, false, true);
7142
                break;
7143
        }
7144
        $return .= '</div>';
7145
7146
        return $return;
7147
    }
7148
7149
    /**
7150
     * Shows the needed forms for editing a specific item.
7151
     *
7152
     * @param CLpItem $lpItem
7153
     *
7154
     * @throws Exception
7155
     * @throws HTML_QuickForm_Error
7156
     *
7157
     * @return string
7158
     */
7159
    public function display_edit_item($lpItem, $excludeExtraFields = [])
7160
    {
7161
        $course_id = api_get_course_int_id();
7162
        $return = '';
7163
7164
        if (empty($lpItem)) {
7165
            return '';
7166
        }
7167
        $item_id = $lpItem->getIid();
7168
        $itemType = $lpItem->getItemType();
7169
        $path = $lpItem->getPath();
7170
7171
        switch ($itemType) {
7172
            case 'dir':
7173
            case 'asset':
7174
            case 'sco':
7175
                if (isset($_GET['view']) && 'build' === $_GET['view']) {
7176
                    $return .= $this->displayItemMenu($lpItem);
7177
                    $return .= $this->display_item_form(
7178
                        $lpItem,
7179
                        'edit'
7180
                    );
7181
                } else {
7182
                    $return .= $this->display_item_form(
7183
                        $lpItem,
7184
                        'edit_item'
7185
                    );
7186
                }
7187
                break;
7188
            case TOOL_LP_FINAL_ITEM:
7189
            case TOOL_DOCUMENT:
7190
            case TOOL_READOUT_TEXT:
7191
                $return .= $this->displayItemMenu($lpItem);
7192
                $return .= $this->displayDocumentForm('edit', $lpItem);
7193
                break;
7194
            case TOOL_LINK:
7195
                $link = null;
7196
                if (!empty($path)) {
7197
                    $repo = Container::getLinkRepository();
7198
                    $link = $repo->find($path);
7199
                }
7200
                $return .= $this->displayItemMenu($lpItem);
7201
                $return .= $this->display_link_form('edit', $lpItem, $link);
7202
7203
                break;
7204
            case TOOL_QUIZ:
7205
                if (!empty($path)) {
7206
                    $repo = Container::getQuizRepository();
7207
                    $resource = $repo->find($path);
7208
                }
7209
                $return .= $this->displayItemMenu($lpItem);
7210
                $return .= $this->display_quiz_form('edit', $lpItem, $resource);
7211
                break;
7212
            /*case TOOL_HOTPOTATOES:
7213
                $return .= $this->displayItemMenu($lpItem);
7214
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7215
                break;*/
7216
            case TOOL_STUDENTPUBLICATION:
7217
                if (!empty($path)) {
7218
                    $repo = Container::getStudentPublicationRepository();
7219
                    $resource = $repo->find($path);
7220
                }
7221
                $return .= $this->displayItemMenu($lpItem);
7222
                $return .= $this->display_student_publication_form('edit', $lpItem, $resource);
7223
                break;
7224
            case TOOL_FORUM:
7225
                if (!empty($path)) {
7226
                    $repo = Container::getForumRepository();
7227
                    $resource = $repo->find($path);
7228
                }
7229
                $return .= $this->displayItemMenu($lpItem);
7230
                $return .= $this->display_forum_form('edit', $lpItem, $resource);
7231
                break;
7232
            case TOOL_THREAD:
7233
                if (!empty($path)) {
7234
                    $repo = Container::getForumPostRepository();
7235
                    $resource = $repo->find($path);
7236
                }
7237
                $return .= $this->displayItemMenu($lpItem);
7238
                $return .= $this->display_thread_form('edit', $lpItem, $resource);
7239
                break;
7240
        }
7241
7242
        return $return;
7243
    }
7244
7245
    /**
7246
     * Function that displays a list with al the resources that
7247
     * could be added to the learning path.
7248
     *
7249
     * @throws Exception
7250
     * @throws HTML_QuickForm_Error
7251
     *
7252
     * @return bool
7253
     */
7254
    public function displayResources()
7255
    {
7256
        // Get all the docs.
7257
        $documents = $this->get_documents(true);
7258
7259
        // Get all the exercises.
7260
        $exercises = $this->get_exercises();
7261
7262
        // Get all the links.
7263
        $links = $this->get_links();
7264
7265
        // Get all the student publications.
7266
        $works = $this->get_student_publications();
7267
7268
        // Get all the forums.
7269
        $forums = $this->get_forums();
7270
7271
        // Get the final item form (see BT#11048) .
7272
        $finish = $this->getFinalItemForm();
7273
7274
        $headers = [
7275
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7276
            Display::return_icon('quiz.png', get_lang('Tests'), [], ICON_SIZE_BIG),
7277
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7278
            Display::return_icon('works.png', get_lang('Assignments'), [], ICON_SIZE_BIG),
7279
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7280
            Display::return_icon('add_learnpath_section.png', get_lang('Add section'), [], ICON_SIZE_BIG),
7281
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7282
        ];
7283
7284
        echo Display::return_message(
7285
            get_lang('Click on the [Learner view] button to see your learning path'),
7286
            'normal'
7287
        );
7288
        $section = $this->displayNewSectionForm();
7289
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
7290
7291
        echo Display::tabs(
7292
            $headers,
7293
            [
7294
                $documents,
7295
                $exercises,
7296
                $links,
7297
                $works,
7298
                $forums,
7299
                $section,
7300
                $finish,
7301
            ],
7302
            'resource_tab',
7303
            [],
7304
            [],
7305
            $selected
7306
        );
7307
7308
        return true;
7309
    }
7310
7311
    /**
7312
     * Returns the extension of a document.
7313
     *
7314
     * @param string $filename
7315
     *
7316
     * @return string Extension (part after the last dot)
7317
     */
7318
    public function get_extension($filename)
7319
    {
7320
        $explode = explode('.', $filename);
7321
7322
        return $explode[count($explode) - 1];
7323
    }
7324
7325
    /**
7326
     * @return string
7327
     */
7328
    public function getCurrentBuildingModeURL()
7329
    {
7330
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
7331
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
7332
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
7333
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
7334
7335
        $currentUrl = api_get_self().'?'.api_get_cidreq().
7336
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
7337
7338
        return $currentUrl;
7339
    }
7340
7341
    /**
7342
     * Displays a document by id.
7343
     *
7344
     * @param CDocument $document
7345
     * @param bool      $show_title
7346
     * @param bool      $iframe
7347
     * @param bool      $edit_link
7348
     *
7349
     * @return string
7350
     */
7351
    public function display_document($document, $show_title = false, $iframe = true, $edit_link = false)
7352
    {
7353
        $return = '';
7354
        if (!$document) {
7355
            return '';
7356
        }
7357
7358
        $repo = Container::getDocumentRepository();
7359
7360
        // TODO: Add a path filter.
7361
        if ($iframe) {
7362
            //$url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.str_replace('%2F', '/', urlencode($row_doc['path'])).'?'.api_get_cidreq();
7363
            $url = $repo->getResourceFileUrl($document);
7364
7365
            $return .= '<iframe
7366
                id="learnpath_preview_frame"
7367
                frameborder="0"
7368
                height="400"
7369
                width="100%"
7370
                scrolling="auto"
7371
                src="'.$url.'"></iframe>';
7372
        } else {
7373
            $return = $repo->getResourceFileContent($document);
7374
        }
7375
7376
        return $return;
7377
    }
7378
7379
    /**
7380
     * Return HTML form to add/edit a link item.
7381
     *
7382
     * @param string  $action (add/edit)
7383
     * @param CLpItem $lpItem
7384
     * @param CLink   $link
7385
     *
7386
     * @throws Exception
7387
     * @throws HTML_QuickForm_Error
7388
     *
7389
     * @return string HTML form
7390
     */
7391
    public function display_link_form($action = 'add', $lpItem, $link)
7392
    {
7393
        $item_url = '';
7394
        if ($link) {
7395
            $item_url = stripslashes($link->getUrl());
7396
        }
7397
        $form = new FormValidator(
7398
            'edit_link',
7399
            'POST',
7400
            $this->getCurrentBuildingModeURL()
7401
        );
7402
7403
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7404
7405
        $urlAttributes = ['class' => 'learnpath_item_form'];
7406
        $urlAttributes['disabled'] = 'disabled';
7407
        $form->addElement('url', 'url', get_lang('URL'), $urlAttributes);
7408
        $form->setDefault('url', $item_url);
7409
7410
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7411
7412
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7413
    }
7414
7415
    /**
7416
     * Return HTML form to add/edit a quiz.
7417
     *
7418
     * @param string  $action   Action (add/edit)
7419
     * @param CLpItem $lpItem   Item ID if already exists
7420
     * @param CQuiz   $exercise Extra information (quiz ID if integer)
7421
     *
7422
     * @throws Exception
7423
     *
7424
     * @return string HTML form
7425
     */
7426
    public function display_quiz_form($action = 'add', $lpItem, $exercise)
7427
    {
7428
        $form = new FormValidator(
7429
            'quiz_form',
7430
            'POST',
7431
            $this->getCurrentBuildingModeURL()
7432
        );
7433
7434
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7435
7436
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7437
7438
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7439
    }
7440
7441
    /**
7442
     * Return the form to display the forum edit/add option.
7443
     *
7444
     * @param CLpItem $lpItem
7445
     *
7446
     * @throws Exception
7447
     *
7448
     * @return string HTML form
7449
     */
7450
    public function display_forum_form($action = 'add', $lpItem, $resource)
7451
    {
7452
        $form = new FormValidator(
7453
            'forum_form',
7454
            'POST',
7455
            $this->getCurrentBuildingModeURL()
7456
        );
7457
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7458
7459
        if ('add' === $action) {
7460
            $form->addButtonSave(get_lang('Add forum to course'), 'submit_button');
7461
        } else {
7462
            $form->addButtonSave(get_lang('Edit the current forum'), 'submit_button');
7463
        }
7464
7465
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7466
    }
7467
7468
    /**
7469
     * Return HTML form to add/edit forum threads.
7470
     *
7471
     * @param string  $action
7472
     * @param CLpItem $lpItem
7473
     * @param string  $resource
7474
     *
7475
     * @throws Exception
7476
     *
7477
     * @return string HTML form
7478
     */
7479
    public function display_thread_form($action = 'add', $lpItem, $resource)
7480
    {
7481
        $form = new FormValidator(
7482
            'thread_form',
7483
            'POST',
7484
            $this->getCurrentBuildingModeURL()
7485
        );
7486
7487
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7488
7489
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7490
7491
        return $form->returnForm();
7492
    }
7493
7494
    /**
7495
     * Return the HTML form to display an item (generally a dir item).
7496
     *
7497
     * @param CLpItem $lpItem
7498
     * @param string  $action
7499
     *
7500
     * @throws Exception
7501
     * @throws HTML_QuickForm_Error
7502
     *
7503
     * @return string HTML form
7504
     */
7505
    public function display_item_form(
7506
        $lpItem,
7507
        $action = 'add_item'
7508
    ) {
7509
        $item_type = $lpItem->getItemType();
7510
7511
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
7512
7513
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
7514
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7515
7516
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
7517
7518
        return $form->returnForm();
7519
    }
7520
7521
    /**
7522
     * Return HTML form to add/edit a student publication (work).
7523
     *
7524
     * @param string              $action
7525
     * @param CStudentPublication $resource
7526
     *
7527
     * @throws Exception
7528
     *
7529
     * @return string HTML form
7530
     */
7531
    public function display_student_publication_form(
7532
        $action = 'add',
7533
        CLpItem $lpItem,
7534
        $resource
7535
    ) {
7536
        $form = new FormValidator('frm_student_publication', 'post', '#');
7537
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7538
7539
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7540
7541
        $return = '<div class="sectioncomment">';
7542
        $return .= $form->returnForm();
7543
        $return .= '</div>';
7544
7545
        return $return;
7546
    }
7547
7548
    public function displayNewSectionForm()
7549
    {
7550
        $action = 'add_item';
7551
        $item_type = 'dir';
7552
7553
        $lpItem = new CLpItem();
7554
        $lpItem->setItemType('dir');
7555
7556
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
7557
7558
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
7559
        LearnPathItemForm::setForm($form, 'add', $this, $lpItem);
7560
7561
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
7562
        $form->addElement('hidden', 'type', 'dir');
7563
7564
        return $form->returnForm();
7565
    }
7566
7567
    /**
7568
     * Returns the form to update or create a document.
7569
     *
7570
     * @param string  $action (add/edit)
7571
     * @param CLpItem $lpItem
7572
     *
7573
     * @throws HTML_QuickForm_Error
7574
     * @throws Exception
7575
     *
7576
     * @return string HTML form
7577
     */
7578
    public function displayDocumentForm($action = 'add', $lpItem = null)
7579
    {
7580
        if (empty($lpItem)) {
7581
            return '';
7582
        }
7583
7584
        $courseInfo = api_get_course_info();
7585
7586
        $form = new FormValidator(
7587
            'form',
7588
            'POST',
7589
            $this->getCurrentBuildingModeURL(),
7590
            '',
7591
            ['enctype' => 'multipart/form-data']
7592
        );
7593
7594
        $data = $this->generate_lp_folder($courseInfo);
7595
7596
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7597
7598
        switch ($action) {
7599
            case 'add':
7600
                $folders = DocumentManager::get_all_document_folders(
7601
                    $courseInfo,
7602
                    0,
7603
                    true
7604
                );
7605
                DocumentManager::build_directory_selector(
7606
                    $folders,
7607
                    '',
7608
                    [],
7609
                    true,
7610
                    $form,
7611
                    'directory_parent_id'
7612
                );
7613
7614
                if (isset($data['id'])) {
7615
                    $defaults['directory_parent_id'] = $data['id'];
7616
                }
7617
7618
                break;
7619
        }
7620
7621
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7622
7623
        return $form->returnForm();
7624
    }
7625
7626
    /**
7627
     * @param array  $courseInfo
7628
     * @param string $content
7629
     * @param string $title
7630
     * @param int    $parentId
7631
     *
7632
     * @throws \Doctrine\ORM\ORMException
7633
     * @throws \Doctrine\ORM\OptimisticLockException
7634
     * @throws \Doctrine\ORM\TransactionRequiredException
7635
     *
7636
     * @return int
7637
     */
7638
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
7639
    {
7640
        $creatorId = api_get_user_id();
7641
        $sessionId = api_get_session_id();
7642
7643
        // Generates folder
7644
        $result = $this->generate_lp_folder($courseInfo);
7645
        $dir = $result['dir'];
7646
7647
        if (empty($parentId) || '/' == $parentId) {
7648
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
7649
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
7650
7651
            if ('/' === $parentId) {
7652
                $dir = '/';
7653
            }
7654
7655
            // Please, do not modify this dirname formatting.
7656
            if (strstr($dir, '..')) {
7657
                $dir = '/';
7658
            }
7659
7660
            if (!empty($dir[0]) && '.' == $dir[0]) {
7661
                $dir = substr($dir, 1);
7662
            }
7663
            if (!empty($dir[0]) && '/' != $dir[0]) {
7664
                $dir = '/'.$dir;
7665
            }
7666
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
7667
                $dir .= '/';
7668
            }
7669
        } else {
7670
            $parentInfo = DocumentManager::get_document_data_by_id(
7671
                $parentId,
7672
                $courseInfo['code']
7673
            );
7674
            if (!empty($parentInfo)) {
7675
                $dir = $parentInfo['path'].'/';
7676
            }
7677
        }
7678
7679
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7680
7681
        if (!is_dir($filepath)) {
7682
            $dir = '/';
7683
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7684
        }
7685
7686
        $originalTitle = !empty($title) ? $title : $_POST['title'];
7687
7688
        if (!empty($title)) {
7689
            $title = api_replace_dangerous_char(stripslashes($title));
7690
        } else {
7691
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
7692
        }
7693
7694
        $title = disable_dangerous_file($title);
7695
        $filename = $title;
7696
        $content = !empty($content) ? $content : $_POST['content_lp'];
7697
        $tmpFileName = $filename;
7698
7699
        $i = 0;
7700
        while (file_exists($filepath.$tmpFileName.'.html')) {
7701
            $tmpFileName = $filename.'_'.++$i;
7702
        }
7703
7704
        $filename = $tmpFileName.'.html';
7705
        $content = stripslashes($content);
7706
7707
        if (file_exists($filepath.$filename)) {
7708
            return 0;
7709
        }
7710
7711
        $putContent = file_put_contents($filepath.$filename, $content);
7712
7713
        if (false === $putContent) {
7714
            return 0;
7715
        }
7716
7717
        $fileSize = filesize($filepath.$filename);
7718
        $saveFilePath = $dir.$filename;
7719
7720
        $document = DocumentManager::addDocument(
7721
            $courseInfo,
7722
            $saveFilePath,
7723
            'file',
7724
            $fileSize,
7725
            $tmpFileName,
7726
            '',
7727
            0, //readonly
7728
            true,
7729
            null,
7730
            $sessionId,
7731
            $creatorId
7732
        );
7733
7734
        $documentId = $document->getId();
7735
7736
        if (!$document) {
7737
            return 0;
7738
        }
7739
7740
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7741
        $newTitle = $originalTitle;
7742
7743
        if ($newComment || $newTitle) {
7744
            $em = Database::getManager();
7745
7746
            if ($newComment) {
7747
                $document->setComment($newComment);
7748
            }
7749
7750
            if ($newTitle) {
7751
                $document->setTitle($newTitle);
7752
            }
7753
7754
            $em->persist($document);
7755
            $em->flush();
7756
        }
7757
7758
        return $documentId;
7759
    }
7760
7761
    /**
7762
     * Displays the menu for manipulating a step.
7763
     *
7764
     * @return string
7765
     */
7766
    public function displayItemMenu(CLpItem $lpItem)
7767
    {
7768
        $item_id = $lpItem->getIid();
7769
        $audio = $lpItem->getAudio();
7770
        $itemType = $lpItem->getItemType();
7771
        $path = $lpItem->getPath();
7772
7773
        $return = '<div class="actions">';
7774
        $audio_player = null;
7775
        // We display an audio player if needed.
7776
        if (!empty($audio)) {
7777
            /*$webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
7778
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
7779
                .'<audio src="'.$webAudioPath.'" controls>'
7780
                .'</div><br>';*/
7781
        }
7782
7783
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
7784
7785
        if (TOOL_LP_FINAL_ITEM !== $itemType) {
7786
            $return .= Display::url(
7787
                Display::return_icon(
7788
                    'edit.png',
7789
                    get_lang('Edit'),
7790
                    [],
7791
                    ICON_SIZE_SMALL
7792
                ),
7793
                $url.'&action=edit_item&path_item='.$path
7794
            );
7795
7796
            /*$return .= Display::url(
7797
                Display::return_icon(
7798
                    'move.png',
7799
                    get_lang('Move'),
7800
                    [],
7801
                    ICON_SIZE_SMALL
7802
                ),
7803
                $url.'&action=move_item'
7804
            );*/
7805
        }
7806
7807
        // Commented for now as prerequisites cannot be added to chapters.
7808
        if ('dir' !== $itemType) {
7809
            $return .= Display::url(
7810
                Display::return_icon(
7811
                    'accept.png',
7812
                    get_lang('Prerequisites'),
7813
                    [],
7814
                    ICON_SIZE_SMALL
7815
                ),
7816
                $url.'&action=edit_item_prereq'
7817
            );
7818
        }
7819
        $return .= Display::url(
7820
            Display::return_icon(
7821
                'delete.png',
7822
                get_lang('Delete'),
7823
                [],
7824
                ICON_SIZE_SMALL
7825
            ),
7826
            $url.'&action=delete_item'
7827
        );
7828
7829
        /*if (in_array($itemType, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
7830
            $documentData = DocumentManager::get_document_data_by_id($path, $course_code);
7831
            if (empty($documentData)) {
7832
                // Try with iid
7833
                $table = Database::get_course_table(TABLE_DOCUMENT);
7834
                $sql = "SELECT path FROM $table
7835
                        WHERE
7836
                              c_id = ".api_get_course_int_id()." AND
7837
                              iid = ".$path." AND
7838
                              path NOT LIKE '%_DELETED_%'";
7839
                $result = Database::query($sql);
7840
                $documentData = Database::fetch_array($result);
7841
                if ($documentData) {
7842
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
7843
                }
7844
            }
7845
            if (isset($documentData['absolute_path_from_document'])) {
7846
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
7847
            }
7848
        }*/
7849
7850
        $return .= '</div>';
7851
7852
        if (!empty($audio_player)) {
7853
            $return .= $audio_player;
7854
        }
7855
7856
        return $return;
7857
    }
7858
7859
    /**
7860
     * Creates the javascript needed for filling up the checkboxes without page reload.
7861
     *
7862
     * @return string
7863
     */
7864
    public function get_js_dropdown_array()
7865
    {
7866
        $course_id = api_get_course_int_id();
7867
        $return = 'var child_name = new Array();'."\n";
7868
        $return .= 'var child_value = new Array();'."\n\n";
7869
        $return .= 'child_name[0] = new Array();'."\n";
7870
        $return .= 'child_value[0] = new Array();'."\n\n";
7871
7872
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7873
        $sql = "SELECT * FROM ".$tbl_lp_item."
7874
                WHERE
7875
                    c_id = $course_id AND
7876
                    lp_id = ".$this->lp_id." AND
7877
                    parent_item_id = 0
7878
                ORDER BY display_order ASC";
7879
        Database::query($sql);
7880
        $i = 0;
7881
7882
        $list = $this->getItemsForForm(true);
7883
7884
        foreach ($list as $row_zero) {
7885
            if (TOOL_LP_FINAL_ITEM !== $row_zero['item_type']) {
7886
                if (TOOL_QUIZ == $row_zero['item_type']) {
7887
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
7888
                }
7889
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
7890
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
7891
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
7892
            }
7893
        }
7894
7895
        $return .= "\n";
7896
        $sql = "SELECT * FROM $tbl_lp_item
7897
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7898
        $res = Database::query($sql);
7899
        while ($row = Database::fetch_array($res)) {
7900
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
7901
                           WHERE
7902
                                c_id = ".$course_id." AND
7903
                                parent_item_id = ".$row['iid']."
7904
                           ORDER BY display_order ASC";
7905
            $res_parent = Database::query($sql_parent);
7906
            $i = 0;
7907
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
7908
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
7909
7910
            while ($row_parent = Database::fetch_array($res_parent)) {
7911
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
7912
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
7913
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
7914
            }
7915
            $return .= "\n";
7916
        }
7917
7918
        $return .= "
7919
            function load_cbo(id) {
7920
                if (!id) {
7921
                    return false;
7922
                }
7923
7924
                var cbo = document.getElementById('previous');
7925
                for(var i = cbo.length - 1; i > 0; i--) {
7926
                    cbo.options[i] = null;
7927
                }
7928
7929
                var k=0;
7930
                for(var i = 1; i <= child_name[id].length; i++){
7931
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
7932
                    option.style.paddingLeft = '40px';
7933
                    cbo.options[i] = option;
7934
                    k = i;
7935
                }
7936
7937
                cbo.options[k].selected = true;
7938
                //$('#previous').selectpicker('refresh');
7939
            }";
7940
7941
        return $return;
7942
    }
7943
7944
    /**
7945
     * Display the form to allow moving an item.
7946
     *
7947
     * @param CLpItem $lpItem
7948
     *
7949
     * @throws Exception
7950
     * @throws HTML_QuickForm_Error
7951
     *
7952
     * @return string HTML form
7953
     */
7954
    public function display_move_item($lpItem)
7955
    {
7956
        $return = '';
7957
        $path = $lpItem->getPath();
7958
7959
        if ($lpItem) {
7960
            $itemType = $lpItem->getItemType();
7961
            switch ($itemType) {
7962
                case 'dir':
7963
                case 'asset':
7964
                    $return .= $this->displayItemMenu($lpItem);
7965
                    $return .= $this->display_item_form(
7966
                        $lpItem,
7967
                        get_lang('Move the current section'),
7968
                        'move',
7969
                        $row
7970
                    );
7971
                    break;
7972
                case TOOL_DOCUMENT:
7973
                    $return .= $this->displayItemMenu($lpItem);
7974
                    $return .= $this->displayDocumentForm('move', $lpItem);
7975
                    break;
7976
                case TOOL_LINK:
7977
                    $link = null;
7978
                    if (!empty($path)) {
7979
                        $repo = Container::getLinkRepository();
7980
                        $link = $repo->find($path);
7981
                    }
7982
                    $return .= $this->displayItemMenu($lpItem);
7983
                    $return .= $this->display_link_form('move', $lpItem, $link);
7984
                    break;
7985
                case TOOL_HOTPOTATOES:
7986
                    $return .= $this->displayItemMenu($lpItem);
7987
                    $return .= $this->display_link_form('move', $lpItem, $row);
7988
                    break;
7989
                case TOOL_QUIZ:
7990
                    $return .= $this->displayItemMenu($lpItem);
7991
                    $return .= $this->display_quiz_form('move', $lpItem, $row);
7992
                    break;
7993
                case TOOL_STUDENTPUBLICATION:
7994
                    $return .= $this->displayItemMenu($lpItem);
7995
                    $return .= $this->display_student_publication_form('move', $lpItem, $row);
7996
                    break;
7997
                case TOOL_FORUM:
7998
                    $return .= $this->displayItemMenu($lpItem);
7999
                    $return .= $this->display_forum_form('move', $lpItem, $row);
8000
                    break;
8001
                case TOOL_THREAD:
8002
                    $return .= $this->displayItemMenu($lpItem);
8003
                    $return .= $this->display_forum_form('move', $lpItem, $row);
8004
                    break;
8005
            }
8006
        }
8007
8008
        return $return;
8009
    }
8010
8011
    /**
8012
     * Return HTML form to allow prerequisites selection.
8013
     *
8014
     * @todo use FormValidator
8015
     *
8016
     * @return string HTML form
8017
     */
8018
    public function display_item_prerequisites_form(CLpItem $lpItem)
8019
    {
8020
        $course_id = api_get_course_int_id();
8021
        $prerequisiteId = $lpItem->getPrerequisite();
8022
        $itemId = $lpItem->getIid();
8023
8024
        $return = '<legend>';
8025
        $return .= get_lang('Add/edit prerequisites');
8026
        $return .= '</legend>';
8027
        $return .= '<form method="POST">';
8028
        $return .= '<div class="table-responsive">';
8029
        $return .= '<table class="table table-hover">';
8030
        $return .= '<thead>';
8031
        $return .= '<tr>';
8032
        $return .= '<th>'.get_lang('Prerequisites').'</th>';
8033
        $return .= '<th width="140">'.get_lang('minimum').'</th>';
8034
        $return .= '<th width="140">'.get_lang('maximum').'</th>';
8035
        $return .= '</tr>';
8036
        $return .= '</thead>';
8037
8038
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
8039
        $return .= '<tbody>';
8040
        $return .= '<tr>';
8041
        $return .= '<td colspan="3">';
8042
        $return .= '<div class="radio learnpath"><label for="idnone">';
8043
        $return .= '<input checked="checked" id="idnone" name="prerequisites" type="radio" />';
8044
        $return .= get_lang('none').'</label>';
8045
        $return .= '</div>';
8046
        $return .= '</tr>';
8047
8048
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8049
        $sql = "SELECT * FROM $tbl_lp_item
8050
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
8051
        $result = Database::query($sql);
8052
8053
        $selectedMinScore = [];
8054
        $selectedMaxScore = [];
8055
        $masteryScore = [];
8056
        while ($row = Database::fetch_array($result)) {
8057
            if ($row['iid'] == $itemId) {
8058
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
8059
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
8060
            }
8061
            $masteryScore[$row['iid']] = $row['mastery_score'];
8062
        }
8063
8064
        $arrLP = $this->getItemsForForm();
8065
        $this->tree_array($arrLP);
8066
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8067
        unset($this->arrMenu);
8068
8069
        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...
8070
            $item = $arrLP[$i];
8071
8072
            if ($item['id'] == $itemId) {
8073
                break;
8074
            }
8075
8076
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
8077
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
8078
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
8079
8080
            $return .= '<tr>';
8081
            $return .= '<td '.((TOOL_QUIZ != $item['item_type'] && TOOL_HOTPOTATOES != $item['item_type']) ? ' colspan="3"' : '').'>';
8082
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
8083
            $return .= '<label for="id'.$item['id'].'">';
8084
8085
            $checked = '';
8086
            if (null !== $prerequisiteId) {
8087
                $checked = in_array($prerequisiteId, [$item['id'], $item['ref']]) ? ' checked="checked" ' : '';
8088
            }
8089
8090
            $disabled = 'dir' === $item['item_type'] ? ' disabled="disabled" ' : '';
8091
8092
            $return .= '<input '.$checked.' '.$disabled.' id="id'.$item['id'].'" name="prerequisites" type="radio" value="'.$item['id'].'" />';
8093
8094
            $icon_name = str_replace(' ', '', $item['item_type']);
8095
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
8096
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
8097
            } else {
8098
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
8099
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
8100
                } else {
8101
                    $return .= Display::return_icon('folder_document.png');
8102
                }
8103
            }
8104
8105
            $return .= $item['title'].'</label>';
8106
            $return .= '</div>';
8107
            $return .= '</td>';
8108
8109
            if (TOOL_QUIZ == $item['item_type']) {
8110
                // lets update max_score Tests information depending of the Tests Advanced properties
8111
                $lpItemObj = new LpItem($course_id, $item['id']);
8112
                $exercise = new Exercise($course_id);
8113
                $exercise->read($lpItemObj->path);
8114
                $lpItemObj->max_score = $exercise->get_max_score();
8115
                $lpItemObj->update();
8116
                $item['max_score'] = $lpItemObj->max_score;
8117
8118
                if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
8119
                    // Backwards compatibility with 1.9.x use mastery_score as min value
8120
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
8121
                }
8122
8123
                $return .= '<td>';
8124
                $return .= '<input
8125
                    class="form-control"
8126
                    size="4" maxlength="3"
8127
                    name="min_'.$item['id'].'"
8128
                    type="number"
8129
                    min="0"
8130
                    step="any"
8131
                    max="'.$item['max_score'].'"
8132
                    value="'.$selectedMinScoreValue.'"
8133
                />';
8134
                $return .= '</td>';
8135
                $return .= '<td>';
8136
                $return .= '<input
8137
                    class="form-control"
8138
                    size="4"
8139
                    maxlength="3"
8140
                    name="max_'.$item['id'].'"
8141
                    type="number"
8142
                    min="0"
8143
                    step="any"
8144
                    max="'.$item['max_score'].'"
8145
                    value="'.$selectedMaxScoreValue.'"
8146
                />';
8147
                $return .= '</td>';
8148
            }
8149
8150
            if (TOOL_HOTPOTATOES == $item['item_type']) {
8151
                $return .= '<td>';
8152
                $return .= '<input
8153
                    size="4"
8154
                    maxlength="3"
8155
                    name="min_'.$item['id'].'"
8156
                    type="number"
8157
                    min="0"
8158
                    step="any"
8159
                    max="'.$item['max_score'].'"
8160
                    value="'.$selectedMinScoreValue.'"
8161
                />';
8162
                $return .= '</td>';
8163
                $return .= '<td>';
8164
                $return .= '<input
8165
                    size="4"
8166
                    maxlength="3"
8167
                    name="max_'.$item['id'].'"
8168
                    type="number"
8169
                    min="0"
8170
                    step="any"
8171
                    max="'.$item['max_score'].'"
8172
                    value="'.$selectedMaxScoreValue.'"
8173
                />';
8174
                $return .= '</td>';
8175
            }
8176
            $return .= '</tr>';
8177
        }
8178
        $return .= '<tr>';
8179
        $return .= '</tr>';
8180
        $return .= '</tbody>';
8181
        $return .= '</table>';
8182
        $return .= '</div>';
8183
        $return .= '<div class="form-group">';
8184
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
8185
            get_lang('Save prerequisites settings').'</button>';
8186
        $return .= '</form>';
8187
8188
        return $return;
8189
    }
8190
8191
    /**
8192
     * Return HTML list to allow prerequisites selection for lp.
8193
     *
8194
     * @return string HTML form
8195
     */
8196
    public function display_lp_prerequisites_list()
8197
    {
8198
        $course_id = api_get_course_int_id();
8199
        $lp_id = $this->lp_id;
8200
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
8201
8202
        // get current prerequisite
8203
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
8204
        $result = Database::query($sql);
8205
        $row = Database::fetch_array($result);
8206
        $prerequisiteId = $row['prerequisite'];
8207
        $session_id = api_get_session_id();
8208
        $session_condition = api_get_session_condition($session_id, true, true);
8209
        $sql = "SELECT * FROM $tbl_lp
8210
                WHERE c_id = $course_id $session_condition
8211
                ORDER BY display_order ";
8212
        $rs = Database::query($sql);
8213
        $return = '';
8214
        $return .= '<select name="prerequisites" class="form-control">';
8215
        $return .= '<option value="0">'.get_lang('none').'</option>';
8216
        if (Database::num_rows($rs) > 0) {
8217
            while ($row = Database::fetch_array($rs)) {
8218
                if ($row['iid'] == $lp_id) {
8219
                    continue;
8220
                }
8221
                $return .= '<option
8222
                    value="'.$row['iid'].'" '.(($row['iid'] == $prerequisiteId) ? ' selected ' : '').'>'.
8223
                    $row['name'].
8224
                    '</option>';
8225
            }
8226
        }
8227
        $return .= '</select>';
8228
8229
        return $return;
8230
    }
8231
8232
    /**
8233
     * Creates a list with all the documents in it.
8234
     *
8235
     * @param bool $showInvisibleFiles
8236
     *
8237
     * @throws Exception
8238
     * @throws HTML_QuickForm_Error
8239
     *
8240
     * @return string
8241
     */
8242
    public function get_documents($showInvisibleFiles = false)
8243
    {
8244
        $course_info = api_get_course_info();
8245
        $sessionId = api_get_session_id();
8246
        $documentTree = DocumentManager::get_document_preview(
8247
            $course_info,
8248
            $this->lp_id,
8249
            null,
8250
            $sessionId,
8251
            true,
8252
            null,
8253
            null,
8254
            $showInvisibleFiles,
8255
            true
8256
        );
8257
8258
        $headers = [
8259
            get_lang('Files'),
8260
            get_lang('CreateTheDocument'),
8261
            get_lang('CreateReadOutText'),
8262
            get_lang('Upload'),
8263
        ];
8264
8265
        $form = new FormValidator(
8266
            'form_upload',
8267
            'POST',
8268
            $this->getCurrentBuildingModeURL(),
8269
            '',
8270
            ['enctype' => 'multipart/form-data']
8271
        );
8272
8273
        $folders = DocumentManager::get_all_document_folders(
8274
            api_get_course_info(),
8275
            0,
8276
            true
8277
        );
8278
8279
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
8280
8281
        DocumentManager::build_directory_selector(
8282
            $folders,
8283
            $lpPathInfo['id'],
8284
            [],
8285
            true,
8286
            $form,
8287
            'directory_parent_id'
8288
        );
8289
8290
        $group = [
8291
            $form->createElement(
8292
                'radio',
8293
                'if_exists',
8294
                get_lang('If file exists:'),
8295
                get_lang('Do nothing'),
8296
                'nothing'
8297
            ),
8298
            $form->createElement(
8299
                'radio',
8300
                'if_exists',
8301
                null,
8302
                get_lang('Overwrite the existing file'),
8303
                'overwrite'
8304
            ),
8305
            $form->createElement(
8306
                'radio',
8307
                'if_exists',
8308
                null,
8309
                get_lang('Rename the uploaded file if it exists'),
8310
                'rename'
8311
            ),
8312
        ];
8313
        $form->addGroup($group, null, get_lang('If file exists:'));
8314
8315
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
8316
        $defaultFileExistsOption = 'rename';
8317
        if (!empty($fileExistsOption)) {
8318
            $defaultFileExistsOption = $fileExistsOption;
8319
        }
8320
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
8321
8322
        // Check box options
8323
        $form->addElement(
8324
            'checkbox',
8325
            'unzip',
8326
            get_lang('Options'),
8327
            get_lang('Uncompress zip')
8328
        );
8329
8330
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
8331
        $form->addMultipleUpload($url);
8332
8333
        $lpItem = new CLpItem();
8334
        $lpItem->setItemType(TOOL_DOCUMENT);
8335
        $new = $this->displayDocumentForm('add', $lpItem);
8336
8337
        /*$lpItem = new CLpItem();
8338
        $lpItem->setItemType(TOOL_READOUT_TEXT);
8339
        $frmReadOutText = $this->displayDocumentForm('add');*/
8340
8341
        $headers = [
8342
            get_lang('Files'),
8343
            get_lang('Create a new document'),
8344
            get_lang('Create read-out text'),
8345
            get_lang('Upload'),
8346
        ];
8347
8348
        return Display::tabs(
8349
            $headers,
8350
            [$documentTree, $new, $form->returnForm()],
8351
            'subtab'
8352
        );
8353
    }
8354
8355
    /**
8356
     * Creates a list with all the exercises (quiz) in it.
8357
     *
8358
     * @return string
8359
     */
8360
    public function get_exercises()
8361
    {
8362
        $course_id = api_get_course_int_id();
8363
        $session_id = api_get_session_id();
8364
        $userInfo = api_get_user_info();
8365
8366
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
8367
        $condition_session = api_get_session_condition($session_id, true, true);
8368
        $setting = 'true' === api_get_setting('lp.show_invisible_exercise_in_lp_toc');
8369
8370
        $activeCondition = ' active <> -1 ';
8371
        if ($setting) {
8372
            $activeCondition = ' active = 1 ';
8373
        }
8374
8375
        $categoryCondition = '';
8376
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
8377
        if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
8378
            $categoryCondition = " AND exercise_category_id = $categoryId ";
8379
        }
8380
8381
        $keywordCondition = '';
8382
        $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
8383
8384
        if (!empty($keyword)) {
8385
            $keyword = Database::escape_string($keyword);
8386
            $keywordCondition = " AND title LIKE '%$keyword%' ";
8387
        }
8388
8389
        $sql_quiz = "SELECT * FROM $tbl_quiz
8390
                     WHERE
8391
                            c_id = $course_id AND
8392
                            $activeCondition
8393
                            $condition_session
8394
                            $categoryCondition
8395
                            $keywordCondition
8396
                     ORDER BY title ASC";
8397
        $res_quiz = Database::query($sql_quiz);
8398
8399
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
8400
8401
        // Create a search-box
8402
        $form = new FormValidator('search_simple', 'get', $currentUrl);
8403
        $form->addHidden('action', 'add_item');
8404
        $form->addHidden('type', 'step');
8405
        $form->addHidden('lp_id', $this->lp_id);
8406
        $form->addHidden('lp_build_selected', '2');
8407
8408
        $form->addCourseHiddenParams();
8409
        $form->addText(
8410
            'keyword',
8411
            get_lang('Search'),
8412
            false,
8413
            [
8414
                'aria-label' => get_lang('Search'),
8415
            ]
8416
        );
8417
8418
        if (api_get_configuration_value('allow_exercise_categories')) {
8419
            $manager = new ExerciseCategoryManager();
8420
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
8421
            if (!empty($options)) {
8422
                $form->addSelect(
8423
                    'category_id',
8424
                    get_lang('Category'),
8425
                    $options,
8426
                    ['placeholder' => get_lang('Please select an option')]
8427
                );
8428
            }
8429
        }
8430
8431
        $form->addButtonSearch(get_lang('Search'));
8432
        $return = $form->returnForm();
8433
8434
        $return .= '<ul class="lp_resource">';
8435
        $return .= '<li class="lp_resource_element">';
8436
        $return .= Display::return_icon('new_exercice.png');
8437
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
8438
            get_lang('New test').'</a>';
8439
        $return .= '</li>';
8440
8441
        $previewIcon = Display::return_icon(
8442
            'preview_view.png',
8443
            get_lang('Preview')
8444
        );
8445
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
8446
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8447
8448
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
8449
        $repo = Container::getQuizRepository();
8450
        $courseEntity = api_get_course_entity();
8451
        $sessionEntity = api_get_session_entity();
8452
        while ($row_quiz = Database::fetch_array($res_quiz)) {
8453
            $exerciseId = $row_quiz['iid'];
8454
            /** @var CQuiz $exercise */
8455
            $exercise = $repo->find($exerciseId);
8456
            $title = strip_tags(api_html_entity_decode($row_quiz['title']));
8457
8458
            $visibility = $exercise->isVisible($courseEntity, $sessionEntity);
8459
            /*$visibility = api_get_item_visibility(
8460
                ['real_id' => $course_id],
8461
                TOOL_QUIZ,
8462
                $row_quiz['iid'],
8463
                $session_id
8464
            );*/
8465
8466
            $link = Display::url(
8467
                $previewIcon,
8468
                $exerciseUrl.'&exerciseId='.$exerciseId,
8469
                ['target' => '_blank']
8470
            );
8471
            $return .= '<li class="lp_resource_element" data_id="'.$exerciseId.'" data_type="quiz" title="'.$title.'" >';
8472
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
8473
            $return .= $quizIcon;
8474
            $sessionStar = api_get_session_image(
8475
                $row_quiz['session_id'],
8476
                $userInfo['status']
8477
            );
8478
            $return .= Display::url(
8479
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
8480
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$exerciseId.'&lp_id='.$this->lp_id,
8481
                [
8482
                    'class' => false === $visibility ? 'moved text-muted' : 'moved',
8483
                ]
8484
            );
8485
            $return .= '</li>';
8486
        }
8487
8488
        $return .= '</ul>';
8489
8490
        return $return;
8491
    }
8492
8493
    /**
8494
     * Creates a list with all the links in it.
8495
     *
8496
     * @return string
8497
     */
8498
    public function get_links()
8499
    {
8500
        $sessionId = api_get_session_id();
8501
        $repo = Container::getLinkRepository();
8502
8503
        $course = api_get_course_entity();
8504
        $session = api_get_session_entity($sessionId);
8505
        $qb = $repo->getResourcesByCourse($course, $session);
8506
        /** @var CLink[] $links */
8507
        $links = $qb->getQuery()->getResult();
8508
8509
        $selfUrl = api_get_self();
8510
        $courseIdReq = api_get_cidreq();
8511
        $userInfo = api_get_user_info();
8512
8513
        $moveEverywhereIcon = Display::return_icon(
8514
            'move_everywhere.png',
8515
            get_lang('Move'),
8516
            [],
8517
            ICON_SIZE_TINY
8518
        );
8519
8520
        /*$condition_session = api_get_session_condition(
8521
            $session_id,
8522
            true,
8523
            true,
8524
            'link.session_id'
8525
        );
8526
8527
        $sql = "SELECT
8528
                    link.id as link_id,
8529
                    link.title as link_title,
8530
                    link.session_id as link_session_id,
8531
                    link.category_id as category_id,
8532
                    link_category.category_title as category_title
8533
                FROM $tbl_link as link
8534
                LEFT JOIN $linkCategoryTable as link_category
8535
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
8536
                WHERE link.c_id = $course_id $condition_session
8537
                ORDER BY link_category.category_title ASC, link.title ASC";
8538
        $result = Database::query($sql);*/
8539
        $categorizedLinks = [];
8540
        $categories = [];
8541
8542
        foreach ($links as $link) {
8543
            $categoryId = null !== $link->getCategory() ? $link->getCategory()->getIid() : 0;
8544
8545
            if (empty($categoryId)) {
8546
                $categories[0] = get_lang('Uncategorized');
8547
            } else {
8548
                $category = $link->getCategory();
8549
                $categories[$categoryId] = $category->getCategoryTitle();
8550
            }
8551
            $categorizedLinks[$categoryId][$link->getIid()] = $link;
8552
        }
8553
8554
        $linksHtmlCode =
8555
            '<script>
8556
            function toggle_tool(tool, id) {
8557
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
8558
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
8559
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
8560
                } else {
8561
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
8562
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
8563
                }
8564
            }
8565
        </script>
8566
8567
        <ul class="lp_resource">
8568
            <li class="lp_resource_element">
8569
                '.Display::return_icon('linksnew.gif').'
8570
                <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').'">'.
8571
                get_lang('Add a link').'
8572
                </a>
8573
            </li>';
8574
8575
        foreach ($categorizedLinks as $categoryId => $links) {
8576
            $linkNodes = null;
8577
            /** @var CLink $link */
8578
            foreach ($links as $key => $link) {
8579
                $title = $link->getTitle();
8580
8581
                $linkUrl = Display::url(
8582
                    Display::return_icon('preview_view.png', get_lang('Preview')),
8583
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
8584
                    ['target' => '_blank']
8585
                );
8586
8587
                if ($link->isVisible($course, $session)) {
8588
                    //$sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
8589
                    $sessionStar = '';
8590
                    $linkNodes .=
8591
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
8592
                        <a class="moved" href="#">'.
8593
                            $moveEverywhereIcon.
8594
                        '</a>
8595
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
8596
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
8597
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
8598
                        Security::remove_XSS($title).$sessionStar.$linkUrl.
8599
                        '</a>
8600
                    </li>';
8601
                }
8602
            }
8603
            $linksHtmlCode .=
8604
                '<li>
8605
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
8606
                    <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
8607
                    align="absbottom" />
8608
                </a>
8609
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
8610
            </li>
8611
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
8612
        }
8613
        $linksHtmlCode .= '</ul>';
8614
8615
        return $linksHtmlCode;
8616
    }
8617
8618
    /**
8619
     * Creates a list with all the student publications in it.
8620
     *
8621
     * @return string
8622
     */
8623
    public function get_student_publications()
8624
    {
8625
        $return = '<ul class="lp_resource">';
8626
        $return .= '<li class="lp_resource_element">';
8627
        /*
8628
        $return .= Display::return_icon('works_new.gif');
8629
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
8630
            get_lang('Add the Assignments tool to the course').'</a>';
8631
        $return .= '</li>';*/
8632
8633
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
8634
        $works = getWorkListTeacher(0, 100, null, null, null);
8635
        if (!empty($works)) {
8636
            foreach ($works as $work) {
8637
                $link = Display::url(
8638
                    Display::return_icon('preview_view.png', get_lang('Preview')),
8639
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
8640
                    ['target' => '_blank']
8641
                );
8642
8643
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
8644
                $return .= '<a class="moved" href="#">';
8645
                $return .= Display::return_icon(
8646
                    'move_everywhere.png',
8647
                    get_lang('Move'),
8648
                    [],
8649
                    ICON_SIZE_TINY
8650
                );
8651
                $return .= '</a> ';
8652
8653
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
8654
                $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.'">'.
8655
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
8656
                </a>';
8657
8658
                $return .= '</li>';
8659
            }
8660
        }
8661
8662
        $return .= '</ul>';
8663
8664
        return $return;
8665
    }
8666
8667
    /**
8668
     * Creates a list with all the forums in it.
8669
     *
8670
     * @return string
8671
     */
8672
    public function get_forums()
8673
    {
8674
        require_once '../forum/forumfunction.inc.php';
8675
8676
        $forumCategories = get_forum_categories();
8677
        $forumsInNoCategory = get_forums_in_category(0);
8678
        if (!empty($forumsInNoCategory)) {
8679
            $forumCategories = array_merge(
8680
                $forumCategories,
8681
                [
8682
                    [
8683
                        'cat_id' => 0,
8684
                        'session_id' => 0,
8685
                        'visibility' => 1,
8686
                        'cat_comment' => null,
8687
                    ],
8688
                ]
8689
            );
8690
        }
8691
8692
        $a_forums = [];
8693
        $courseEntity = api_get_course_entity(api_get_course_int_id());
8694
        $sessionEntity = api_get_session_entity(api_get_session_id());
8695
8696
        foreach ($forumCategories as $forumCategory) {
8697
            // The forums in this category.
8698
            $forumsInCategory = get_forums_in_category($forumCategory->getIid());
8699
            if (!empty($forumsInCategory)) {
8700
                foreach ($forumsInCategory as $forum) {
8701
                    if ($forum->isVisible($courseEntity, $sessionEntity)) {
8702
                        $a_forums[] = $forum;
8703
                    }
8704
                }
8705
            }
8706
        }
8707
8708
        $return = '<ul class="lp_resource">';
8709
8710
        // First add link
8711
        $return .= '<li class="lp_resource_element">';
8712
        $return .= Display::return_icon('new_forum.png');
8713
        $return .= Display::url(
8714
            get_lang('Create a new forum'),
8715
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
8716
                'action' => 'add',
8717
                'content' => 'forum',
8718
                'lp_id' => $this->lp_id,
8719
            ]),
8720
            ['title' => get_lang('Create a new forum')]
8721
        );
8722
        $return .= '</li>';
8723
8724
        $return .= '<script>
8725
            function toggle_forum(forum_id) {
8726
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
8727
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
8728
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
8729
                } else {
8730
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
8731
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
8732
                }
8733
            }
8734
        </script>';
8735
8736
        foreach ($a_forums as $forum) {
8737
            $forumId = $forum->getIid();
8738
            $title = Security::remove_XSS($forum->getForumTitle());
8739
            $link = Display::url(
8740
                Display::return_icon('preview_view.png', get_lang('Preview')),
8741
                api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forumId,
8742
                ['target' => '_blank']
8743
            );
8744
8745
            $return .= '<li class="lp_resource_element" data_id="'.$forumId.'" data_type="'.TOOL_FORUM.'" title="'.$title.'" >';
8746
            $return .= '<a class="moved" href="#">';
8747
            $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8748
            $return .= ' </a>';
8749
            $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
8750
            $return .= '<a onclick="javascript:toggle_forum('.$forumId.');" style="cursor:hand; vertical-align:middle">
8751
                            <img src="'.Display::returnIconPath('add.png').'" id="forum_'.$forumId.'_opener" align="absbottom" />
8752
                        </a>
8753
                        <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">'.
8754
                $title.' '.$link.'</a>';
8755
8756
            $return .= '</li>';
8757
8758
            $return .= '<div style="display:none" id="forum_'.$forumId.'_content">';
8759
            $threads = get_threads($forumId);
8760
            if (is_array($threads)) {
8761
                foreach ($threads as $thread) {
8762
                    $threadId = $thread->getIid();
8763
                    $link = Display::url(
8764
                        Display::return_icon('preview_view.png', get_lang('Preview')),
8765
                        api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forumId.'&thread='.$threadId,
8766
                        ['target' => '_blank']
8767
                    );
8768
8769
                    $return .= '<li class="lp_resource_element" data_id="'.$thread->getIid().'" data_type="'.TOOL_THREAD.'" title="'.$thread->getThreadTitle().'" >';
8770
                    $return .= '&nbsp;<a class="moved" href="#">';
8771
                    $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8772
                    $return .= ' </a>';
8773
                    $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
8774
                    $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.'">'.
8775
                        Security::remove_XSS($thread->getThreadTitle()).' '.$link.'</a>';
8776
                    $return .= '</li>';
8777
                }
8778
            }
8779
            $return .= '</div>';
8780
        }
8781
        $return .= '</ul>';
8782
8783
        return $return;
8784
    }
8785
8786
    /**
8787
     * // TODO: The output encoding should be equal to the system encoding.
8788
     *
8789
     * Exports the learning path as a SCORM package. This is the main function that
8790
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
8791
     * whole thing and returns the zip.
8792
     *
8793
     * This method needs to be called in PHP5, as it will fail with non-adequate
8794
     * XML package (like the ones for PHP4), and it is *not* a static method, so
8795
     * you need to call it on a learnpath object.
8796
     *
8797
     * @TODO The method might be redefined later on in the scorm class itself to avoid
8798
     * creating a SCORM structure if there is one already. However, if the initial SCORM
8799
     * path has been modified, it should use the generic method here below.
8800
     *
8801
     * @return string Returns the zip package string, or null if error
8802
     */
8803
    public function scormExport()
8804
    {
8805
        api_set_more_memory_and_time_limits();
8806
8807
        $_course = api_get_course_info();
8808
        $course_id = $_course['real_id'];
8809
        // Create the zip handler (this will remain available throughout the method).
8810
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
8811
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
8812
        $temp_dir_short = uniqid('scorm_export', true);
8813
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
8814
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
8815
        $zip_folder = new PclZip($temp_zip_file);
8816
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
8817
        $root_path = $main_path = api_get_path(SYS_PATH);
8818
        $files_cleanup = [];
8819
8820
        // Place to temporarily stash the zip file.
8821
        // create the temp dir if it doesn't exist
8822
        // or do a cleanup before creating the zip file.
8823
        if (!is_dir($temp_zip_dir)) {
8824
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
8825
        } else {
8826
            // Cleanup: Check the temp dir for old files and delete them.
8827
            $handle = opendir($temp_zip_dir);
8828
            while (false !== ($file = readdir($handle))) {
8829
                if ('.' != $file && '..' != $file) {
8830
                    unlink("$temp_zip_dir/$file");
8831
                }
8832
            }
8833
            closedir($handle);
8834
        }
8835
        $zip_files = $zip_files_abs = $zip_files_dist = [];
8836
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
8837
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
8838
        ) {
8839
            // Remove the possible . at the end of the path.
8840
            $dest_path_to_lp = '.' == substr($this->path, -1) ? substr($this->path, 0, -1) : $this->path;
8841
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
8842
            mkdir(
8843
                $dest_path_to_scorm_folder,
8844
                api_get_permissions_for_new_directories(),
8845
                true
8846
            );
8847
            copyr(
8848
                $current_course_path.'/scorm/'.$this->path,
8849
                $dest_path_to_scorm_folder,
8850
                ['imsmanifest'],
8851
                $zip_files
8852
            );
8853
        }
8854
8855
        // Build a dummy imsmanifest structure.
8856
        // Do not add to the zip yet (we still need it).
8857
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
8858
        // Aggregation Model official document, section "2.3 Content Packaging".
8859
        // We are going to build a UTF-8 encoded manifest.
8860
        // Later we will recode it to the desired (and supported) encoding.
8861
        $xmldoc = new DOMDocument('1.0');
8862
        $root = $xmldoc->createElement('manifest');
8863
        $root->setAttribute('identifier', 'SingleCourseManifest');
8864
        $root->setAttribute('version', '1.1');
8865
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
8866
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
8867
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
8868
        $root->setAttribute(
8869
            'xsi:schemaLocation',
8870
            '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'
8871
        );
8872
        // Build mandatory sub-root container elements.
8873
        $metadata = $xmldoc->createElement('metadata');
8874
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
8875
        $metadata->appendChild($md_schema);
8876
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
8877
        $metadata->appendChild($md_schemaversion);
8878
        $root->appendChild($metadata);
8879
8880
        $organizations = $xmldoc->createElement('organizations');
8881
        $resources = $xmldoc->createElement('resources');
8882
8883
        // Build the only organization we will use in building our learnpaths.
8884
        $organizations->setAttribute('default', 'chamilo_scorm_export');
8885
        $organization = $xmldoc->createElement('organization');
8886
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
8887
        // To set the title of the SCORM entity (=organization), we take the name given
8888
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
8889
        // learning path charset) as it is the encoding that defines how it is stored
8890
        // in the database. Then we convert it to HTML entities again as the "&" character
8891
        // alone is not authorized in XML (must be &amp;).
8892
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
8893
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
8894
        $organization->appendChild($org_title);
8895
        $folder_name = 'document';
8896
8897
        // Removes the learning_path/scorm_folder path when exporting see #4841
8898
        $path_to_remove = '';
8899
        $path_to_replace = '';
8900
        $result = $this->generate_lp_folder($_course);
8901
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
8902
            $path_to_remove = 'document'.$result['dir'];
8903
            $path_to_replace = $folder_name.'/';
8904
        }
8905
8906
        // Fixes chamilo scorm exports
8907
        if ('chamilo_scorm_export' === $this->ref) {
8908
            $path_to_remove = 'scorm/'.$this->path.'/document/';
8909
        }
8910
8911
        // For each element, add it to the imsmanifest structure, then add it to the zip.
8912
        $link_updates = [];
8913
        $links_to_create = [];
8914
        foreach ($this->ordered_items as $index => $itemId) {
8915
            /** @var learnpathItem $item */
8916
            $item = $this->items[$itemId];
8917
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
8918
                // Get included documents from this item.
8919
                if ('sco' === $item->type) {
8920
                    $inc_docs = $item->get_resources_from_source(
8921
                        null,
8922
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
8923
                    );
8924
                } else {
8925
                    $inc_docs = $item->get_resources_from_source();
8926
                }
8927
8928
                // Give a child element <item> to the <organization> element.
8929
                $my_item_id = $item->get_id();
8930
                $my_item = $xmldoc->createElement('item');
8931
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
8932
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
8933
                $my_item->setAttribute('isvisible', 'true');
8934
                // Give a child element <title> to the <item> element.
8935
                $my_title = $xmldoc->createElement(
8936
                    'title',
8937
                    htmlspecialchars(
8938
                        api_utf8_encode($item->get_title()),
8939
                        ENT_QUOTES,
8940
                        'UTF-8'
8941
                    )
8942
                );
8943
                $my_item->appendChild($my_title);
8944
                // Give a child element <adlcp:prerequisites> to the <item> element.
8945
                $my_prereqs = $xmldoc->createElement(
8946
                    'adlcp:prerequisites',
8947
                    $this->get_scorm_prereq_string($my_item_id)
8948
                );
8949
                $my_prereqs->setAttribute('type', 'aicc_script');
8950
                $my_item->appendChild($my_prereqs);
8951
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
8952
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
8953
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
8954
                //$xmldoc->createElement('adlcp:timelimitaction','');
8955
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
8956
                //$xmldoc->createElement('adlcp:datafromlms','');
8957
                // Give a child element <adlcp:masteryscore> to the <item> element.
8958
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
8959
                $my_item->appendChild($my_masteryscore);
8960
8961
                // Attach this item to the organization element or hits parent if there is one.
8962
                if (!empty($item->parent) && 0 != $item->parent) {
8963
                    $children = $organization->childNodes;
8964
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
8965
                    if (is_object($possible_parent)) {
8966
                        $possible_parent->appendChild($my_item);
8967
                    } else {
8968
                        if ($this->debug > 0) {
8969
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
8970
                        }
8971
                    }
8972
                } else {
8973
                    if ($this->debug > 0) {
8974
                        error_log('No parent');
8975
                    }
8976
                    $organization->appendChild($my_item);
8977
                }
8978
8979
                // Get the path of the file(s) from the course directory root.
8980
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
8981
                $my_xml_file_path = $my_file_path;
8982
                if (!empty($path_to_remove)) {
8983
                    // From docs
8984
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
8985
8986
                    // From quiz
8987
                    if ('chamilo_scorm_export' === $this->ref) {
8988
                        $path_to_remove = 'scorm/'.$this->path.'/';
8989
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
8990
                    }
8991
                }
8992
8993
                $my_sub_dir = dirname($my_file_path);
8994
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
8995
                $my_xml_sub_dir = $my_sub_dir;
8996
                // Give a <resource> child to the <resources> element
8997
                $my_resource = $xmldoc->createElement('resource');
8998
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
8999
                $my_resource->setAttribute('type', 'webcontent');
9000
                $my_resource->setAttribute('href', $my_xml_file_path);
9001
                // adlcp:scormtype can be either 'sco' or 'asset'.
9002
                if ('sco' === $item->type) {
9003
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
9004
                } else {
9005
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
9006
                }
9007
                // xml:base is the base directory to find the files declared in this resource.
9008
                $my_resource->setAttribute('xml:base', '');
9009
                // Give a <file> child to the <resource> element.
9010
                $my_file = $xmldoc->createElement('file');
9011
                $my_file->setAttribute('href', $my_xml_file_path);
9012
                $my_resource->appendChild($my_file);
9013
9014
                // Dependency to other files - not yet supported.
9015
                $i = 1;
9016
                if ($inc_docs) {
9017
                    foreach ($inc_docs as $doc_info) {
9018
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
9019
                            continue;
9020
                        }
9021
                        $my_dep = $xmldoc->createElement('resource');
9022
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
9023
                        $my_dep->setAttribute('identifier', $res_id);
9024
                        $my_dep->setAttribute('type', 'webcontent');
9025
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
9026
                        $my_dep_file = $xmldoc->createElement('file');
9027
                        // Check type of URL.
9028
                        if ('remote' == $doc_info[1]) {
9029
                            // Remote file. Save url as is.
9030
                            $my_dep_file->setAttribute('href', $doc_info[0]);
9031
                            $my_dep->setAttribute('xml:base', '');
9032
                        } elseif ('local' === $doc_info[1]) {
9033
                            switch ($doc_info[2]) {
9034
                                case 'url':
9035
                                    // Local URL - save path as url for now, don't zip file.
9036
                                    $abs_path = api_get_path(SYS_PATH).
9037
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
9038
                                    $current_dir = dirname($abs_path);
9039
                                    $current_dir = str_replace('\\', '/', $current_dir);
9040
                                    $file_path = realpath($abs_path);
9041
                                    $file_path = str_replace('\\', '/', $file_path);
9042
                                    $my_dep_file->setAttribute('href', $file_path);
9043
                                    $my_dep->setAttribute('xml:base', '');
9044
                                    if (false !== strstr($file_path, $main_path)) {
9045
                                        // The calculated real path is really inside Chamilo's root path.
9046
                                        // Reduce file path to what's under the DocumentRoot.
9047
                                        $replace = $file_path;
9048
                                        $file_path = substr($file_path, strlen($root_path) - 1);
9049
                                        $destinationFile = $file_path;
9050
9051
                                        if (false !== strstr($file_path, 'upload/users')) {
9052
                                            $pos = strpos($file_path, 'my_files/');
9053
                                            if (false !== $pos) {
9054
                                                $onlyDirectory = str_replace(
9055
                                                    'upload/users/',
9056
                                                    '',
9057
                                                    substr($file_path, $pos, strlen($file_path))
9058
                                                );
9059
                                            }
9060
                                            $replace = $onlyDirectory;
9061
                                            $destinationFile = $replace;
9062
                                        }
9063
                                        $zip_files_abs[] = $file_path;
9064
                                        $link_updates[$my_file_path][] = [
9065
                                            'orig' => $doc_info[0],
9066
                                            'dest' => $destinationFile,
9067
                                            'replace' => $replace,
9068
                                        ];
9069
                                        $my_dep_file->setAttribute('href', $file_path);
9070
                                        $my_dep->setAttribute('xml:base', '');
9071
                                    } elseif (empty($file_path)) {
9072
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
9073
                                        $file_path = str_replace('//', '/', $file_path);
9074
                                        if (file_exists($file_path)) {
9075
                                            // We get the relative path.
9076
                                            $file_path = substr($file_path, strlen($current_dir));
9077
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
9078
                                            $link_updates[$my_file_path][] = [
9079
                                                'orig' => $doc_info[0],
9080
                                                'dest' => $file_path,
9081
                                            ];
9082
                                            $my_dep_file->setAttribute('href', $file_path);
9083
                                            $my_dep->setAttribute('xml:base', '');
9084
                                        }
9085
                                    }
9086
                                    break;
9087
                                case 'abs':
9088
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
9089
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
9090
                                    $my_dep->setAttribute('xml:base', '');
9091
9092
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
9093
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
9094
                                    $abs_img_path_without_subdir = $doc_info[0];
9095
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
9096
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
9097
                                    if (0 === $pos) {
9098
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
9099
                                    }
9100
9101
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
9102
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
9103
9104
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
9105
                                    $cur_path = '/' == substr($current_course_path, -1) ? $current_course_path : $current_course_path.'/';
9106
                                    // Check if the current document is in that path.
9107
                                    if (false !== strstr($file_path, $cur_path)) {
9108
                                        $destinationFile = substr($file_path, strlen($cur_path));
9109
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
9110
9111
                                        $fileToTest = $cur_path.$my_file_path;
9112
                                        if (!empty($path_to_remove)) {
9113
                                            $fileToTest = str_replace(
9114
                                                $path_to_remove.'/',
9115
                                                $path_to_replace,
9116
                                                $cur_path.$my_file_path
9117
                                            );
9118
                                        }
9119
9120
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
9121
9122
                                        // Put the current document in the zip (this array is the array
9123
                                        // that will manage documents already in the course folder - relative).
9124
                                        $zip_files[] = $filePathNoCoursePart;
9125
                                        // Update the links to the current document in the
9126
                                        // containing document (make them relative).
9127
                                        $link_updates[$my_file_path][] = [
9128
                                            'orig' => $doc_info[0],
9129
                                            'dest' => $destinationFile,
9130
                                            'replace' => $relative_path,
9131
                                        ];
9132
9133
                                        $my_dep_file->setAttribute('href', $file_path);
9134
                                        $my_dep->setAttribute('xml:base', '');
9135
                                    } elseif (false !== strstr($file_path, $main_path)) {
9136
                                        // The calculated real path is really inside Chamilo's root path.
9137
                                        // Reduce file path to what's under the DocumentRoot.
9138
                                        $file_path = substr($file_path, strlen($root_path));
9139
                                        $zip_files_abs[] = $file_path;
9140
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
9141
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
9142
                                        $my_dep->setAttribute('xml:base', '');
9143
                                    } elseif (empty($file_path)) {
9144
                                        // Probably this is an image inside "/main" directory
9145
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
9146
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
9147
9148
                                        if (file_exists($file_path)) {
9149
                                            if (false !== strstr($file_path, 'main/default_course_document')) {
9150
                                                // We get the relative path.
9151
                                                $pos = strpos($file_path, 'main/default_course_document/');
9152
                                                if (false !== $pos) {
9153
                                                    $onlyDirectory = str_replace(
9154
                                                        'main/default_course_document/',
9155
                                                        '',
9156
                                                        substr($file_path, $pos, strlen($file_path))
9157
                                                    );
9158
                                                }
9159
9160
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
9161
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
9162
                                                $zip_files_abs[] = $fileAbs;
9163
                                                $link_updates[$my_file_path][] = [
9164
                                                    'orig' => $doc_info[0],
9165
                                                    'dest' => $destinationFile,
9166
                                                ];
9167
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9168
                                                $my_dep->setAttribute('xml:base', '');
9169
                                            }
9170
                                        }
9171
                                    }
9172
                                    break;
9173
                                case 'rel':
9174
                                    // Path relative to the current document.
9175
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
9176
                                    if ('..' === substr($doc_info[0], 0, 2)) {
9177
                                        // Relative path going up.
9178
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
9179
                                        $current_dir = str_replace('\\', '/', $current_dir);
9180
                                        $file_path = realpath($current_dir.$doc_info[0]);
9181
                                        $file_path = str_replace('\\', '/', $file_path);
9182
                                        if (false !== strstr($file_path, $main_path)) {
9183
                                            // The calculated real path is really inside Chamilo's root path.
9184
                                            // Reduce file path to what's under the DocumentRoot.
9185
                                            $file_path = substr($file_path, strlen($root_path));
9186
                                            $zip_files_abs[] = $file_path;
9187
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
9188
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
9189
                                            $my_dep->setAttribute('xml:base', '');
9190
                                        }
9191
                                    } else {
9192
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
9193
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
9194
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
9195
                                    }
9196
                                    break;
9197
                                default:
9198
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
9199
                                    $my_dep->setAttribute('xml:base', '');
9200
                                    break;
9201
                            }
9202
                        }
9203
                        $my_dep->appendChild($my_dep_file);
9204
                        $resources->appendChild($my_dep);
9205
                        $dependency = $xmldoc->createElement('dependency');
9206
                        $dependency->setAttribute('identifierref', $res_id);
9207
                        $my_resource->appendChild($dependency);
9208
                        $i++;
9209
                    }
9210
                }
9211
                $resources->appendChild($my_resource);
9212
                $zip_files[] = $my_file_path;
9213
            } else {
9214
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
9215
                switch ($item->type) {
9216
                    case TOOL_LINK:
9217
                        $my_item = $xmldoc->createElement('item');
9218
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
9219
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
9220
                        $my_item->setAttribute('isvisible', 'true');
9221
                        // Give a child element <title> to the <item> element.
9222
                        $my_title = $xmldoc->createElement(
9223
                            'title',
9224
                            htmlspecialchars(
9225
                                api_utf8_encode($item->get_title()),
9226
                                ENT_QUOTES,
9227
                                'UTF-8'
9228
                            )
9229
                        );
9230
                        $my_item->appendChild($my_title);
9231
                        // Give a child element <adlcp:prerequisites> to the <item> element.
9232
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
9233
                        $my_prereqs->setAttribute('type', 'aicc_script');
9234
                        $my_item->appendChild($my_prereqs);
9235
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
9236
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
9237
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
9238
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
9239
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
9240
                        //$xmldoc->createElement('adlcp:datafromlms', '');
9241
                        // Give a child element <adlcp:masteryscore> to the <item> element.
9242
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
9243
                        $my_item->appendChild($my_masteryscore);
9244
9245
                        // Attach this item to the organization element or its parent if there is one.
9246
                        if (!empty($item->parent) && 0 != $item->parent) {
9247
                            $children = $organization->childNodes;
9248
                            for ($i = 0; $i < $children->length; $i++) {
9249
                                $item_temp = $children->item($i);
9250
                                if ('item' == $item_temp->nodeName) {
9251
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
9252
                                        $item_temp->appendChild($my_item);
9253
                                    }
9254
                                }
9255
                            }
9256
                        } else {
9257
                            $organization->appendChild($my_item);
9258
                        }
9259
9260
                        $my_file_path = 'link_'.$item->get_id().'.html';
9261
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
9262
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
9263
                        $rs = Database::query($sql);
9264
                        if ($link = Database::fetch_array($rs)) {
9265
                            $url = $link['url'];
9266
                            $title = stripslashes($link['title']);
9267
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
9268
                            $my_xml_file_path = $my_file_path;
9269
                            $my_sub_dir = dirname($my_file_path);
9270
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
9271
                            $my_xml_sub_dir = $my_sub_dir;
9272
                            // Give a <resource> child to the <resources> element.
9273
                            $my_resource = $xmldoc->createElement('resource');
9274
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
9275
                            $my_resource->setAttribute('type', 'webcontent');
9276
                            $my_resource->setAttribute('href', $my_xml_file_path);
9277
                            // adlcp:scormtype can be either 'sco' or 'asset'.
9278
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
9279
                            // xml:base is the base directory to find the files declared in this resource.
9280
                            $my_resource->setAttribute('xml:base', '');
9281
                            // give a <file> child to the <resource> element.
9282
                            $my_file = $xmldoc->createElement('file');
9283
                            $my_file->setAttribute('href', $my_xml_file_path);
9284
                            $my_resource->appendChild($my_file);
9285
                            $resources->appendChild($my_resource);
9286
                        }
9287
                        break;
9288
                    case TOOL_QUIZ:
9289
                        $exe_id = $item->path;
9290
                        // Should be using ref when everything will be cleaned up in this regard.
9291
                        $exe = new Exercise();
9292
                        $exe->read($exe_id);
9293
                        $my_item = $xmldoc->createElement('item');
9294
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
9295
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
9296
                        $my_item->setAttribute('isvisible', 'true');
9297
                        // Give a child element <title> to the <item> element.
9298
                        $my_title = $xmldoc->createElement(
9299
                            'title',
9300
                            htmlspecialchars(
9301
                                api_utf8_encode($item->get_title()),
9302
                                ENT_QUOTES,
9303
                                'UTF-8'
9304
                            )
9305
                        );
9306
                        $my_item->appendChild($my_title);
9307
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
9308
                        $my_item->appendChild($my_max_score);
9309
                        // Give a child element <adlcp:prerequisites> to the <item> element.
9310
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
9311
                        $my_prereqs->setAttribute('type', 'aicc_script');
9312
                        $my_item->appendChild($my_prereqs);
9313
                        // Give a child element <adlcp:masteryscore> to the <item> element.
9314
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
9315
                        $my_item->appendChild($my_masteryscore);
9316
9317
                        // Attach this item to the organization element or hits parent if there is one.
9318
                        if (!empty($item->parent) && 0 != $item->parent) {
9319
                            $children = $organization->childNodes;
9320
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
9321
                            if ($possible_parent) {
9322
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
9323
                                    $possible_parent->appendChild($my_item);
9324
                                }
9325
                            }
9326
                        } else {
9327
                            $organization->appendChild($my_item);
9328
                        }
9329
9330
                        // Get the path of the file(s) from the course directory root
9331
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
9332
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
9333
                        // Write the contents of the exported exercise into a (big) html file
9334
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
9335
                        $scormExercise = new ScormExercise($exe, true);
9336
                        $contents = $scormExercise->export();
9337
9338
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
9339
                        $res = file_put_contents($tmp_file_path, $contents);
9340
                        if (false === $res) {
9341
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
9342
                        }
9343
                        $files_cleanup[] = $tmp_file_path;
9344
                        $my_xml_file_path = $my_file_path;
9345
                        $my_sub_dir = dirname($my_file_path);
9346
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
9347
                        $my_xml_sub_dir = $my_sub_dir;
9348
                        // Give a <resource> child to the <resources> element.
9349
                        $my_resource = $xmldoc->createElement('resource');
9350
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
9351
                        $my_resource->setAttribute('type', 'webcontent');
9352
                        $my_resource->setAttribute('href', $my_xml_file_path);
9353
                        // adlcp:scormtype can be either 'sco' or 'asset'.
9354
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
9355
                        // xml:base is the base directory to find the files declared in this resource.
9356
                        $my_resource->setAttribute('xml:base', '');
9357
                        // Give a <file> child to the <resource> element.
9358
                        $my_file = $xmldoc->createElement('file');
9359
                        $my_file->setAttribute('href', $my_xml_file_path);
9360
                        $my_resource->appendChild($my_file);
9361
9362
                        // Get included docs.
9363
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
9364
9365
                        // Dependency to other files - not yet supported.
9366
                        $i = 1;
9367
                        foreach ($inc_docs as $doc_info) {
9368
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
9369
                                continue;
9370
                            }
9371
                            $my_dep = $xmldoc->createElement('resource');
9372
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
9373
                            $my_dep->setAttribute('identifier', $res_id);
9374
                            $my_dep->setAttribute('type', 'webcontent');
9375
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
9376
                            $my_dep_file = $xmldoc->createElement('file');
9377
                            // Check type of URL.
9378
                            if ('remote' == $doc_info[1]) {
9379
                                // Remote file. Save url as is.
9380
                                $my_dep_file->setAttribute('href', $doc_info[0]);
9381
                                $my_dep->setAttribute('xml:base', '');
9382
                            } elseif ('local' == $doc_info[1]) {
9383
                                switch ($doc_info[2]) {
9384
                                    case 'url': // Local URL - save path as url for now, don't zip file.
9385
                                        // Save file but as local file (retrieve from URL).
9386
                                        $abs_path = api_get_path(SYS_PATH).
9387
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
9388
                                        $current_dir = dirname($abs_path);
9389
                                        $current_dir = str_replace('\\', '/', $current_dir);
9390
                                        $file_path = realpath($abs_path);
9391
                                        $file_path = str_replace('\\', '/', $file_path);
9392
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
9393
                                        $my_dep->setAttribute('xml:base', '');
9394
                                        if (false !== strstr($file_path, $main_path)) {
9395
                                            // The calculated real path is really inside the chamilo root path.
9396
                                            // Reduce file path to what's under the DocumentRoot.
9397
                                            $file_path = substr($file_path, strlen($root_path));
9398
                                            $zip_files_abs[] = $file_path;
9399
                                            $link_updates[$my_file_path][] = [
9400
                                                'orig' => $doc_info[0],
9401
                                                'dest' => 'document/'.$file_path,
9402
                                            ];
9403
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
9404
                                            $my_dep->setAttribute('xml:base', '');
9405
                                        } elseif (empty($file_path)) {
9406
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
9407
                                            $file_path = str_replace('//', '/', $file_path);
9408
                                            if (file_exists($file_path)) {
9409
                                                $file_path = substr($file_path, strlen($current_dir));
9410
                                                // We get the relative path.
9411
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
9412
                                                $link_updates[$my_file_path][] = [
9413
                                                    'orig' => $doc_info[0],
9414
                                                    'dest' => 'document/'.$file_path,
9415
                                                ];
9416
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9417
                                                $my_dep->setAttribute('xml:base', '');
9418
                                            }
9419
                                        }
9420
                                        break;
9421
                                    case 'abs':
9422
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
9423
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
9424
                                        $current_dir = str_replace('\\', '/', $current_dir);
9425
                                        $file_path = realpath($doc_info[0]);
9426
                                        $file_path = str_replace('\\', '/', $file_path);
9427
                                        $my_dep_file->setAttribute('href', $file_path);
9428
                                        $my_dep->setAttribute('xml:base', '');
9429
9430
                                        if (false !== strstr($file_path, $main_path)) {
9431
                                            // The calculated real path is really inside the chamilo root path.
9432
                                            // Reduce file path to what's under the DocumentRoot.
9433
                                            $file_path = substr($file_path, strlen($root_path));
9434
                                            $zip_files_abs[] = $file_path;
9435
                                            $link_updates[$my_file_path][] = [
9436
                                                'orig' => $doc_info[0],
9437
                                                'dest' => $file_path,
9438
                                            ];
9439
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
9440
                                            $my_dep->setAttribute('xml:base', '');
9441
                                        } elseif (empty($file_path)) {
9442
                                            $docSysPartPath = str_replace(
9443
                                                api_get_path(REL_COURSE_PATH),
9444
                                                '',
9445
                                                $doc_info[0]
9446
                                            );
9447
9448
                                            $docSysPartPathNoCourseCode = str_replace(
9449
                                                $_course['directory'].'/',
9450
                                                '',
9451
                                                $docSysPartPath
9452
                                            );
9453
9454
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
9455
                                            if (file_exists($docSysPath)) {
9456
                                                $file_path = $docSysPartPathNoCourseCode;
9457
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
9458
                                                $link_updates[$my_file_path][] = [
9459
                                                    'orig' => $doc_info[0],
9460
                                                    'dest' => $file_path,
9461
                                                ];
9462
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9463
                                                $my_dep->setAttribute('xml:base', '');
9464
                                            }
9465
                                        }
9466
                                        break;
9467
                                    case 'rel':
9468
                                        // Path relative to the current document. Save xml:base as current document's
9469
                                        // directory and save file in zip as subdir.file_path
9470
                                        if ('..' === substr($doc_info[0], 0, 2)) {
9471
                                            // Relative path going up.
9472
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
9473
                                            $current_dir = str_replace('\\', '/', $current_dir);
9474
                                            $file_path = realpath($current_dir.$doc_info[0]);
9475
                                            $file_path = str_replace('\\', '/', $file_path);
9476
                                            if (false !== strstr($file_path, $main_path)) {
9477
                                                // The calculated real path is really inside Chamilo's root path.
9478
                                                // Reduce file path to what's under the DocumentRoot.
9479
9480
                                                $file_path = substr($file_path, strlen($root_path));
9481
                                                $file_path_dest = $file_path;
9482
9483
                                                // File path is courses/CHAMILO/document/....
9484
                                                $info_file_path = explode('/', $file_path);
9485
                                                if ('courses' == $info_file_path[0]) {
9486
                                                    // Add character "/" in file path.
9487
                                                    $file_path_dest = 'document/'.$file_path;
9488
                                                }
9489
                                                $zip_files_abs[] = $file_path;
9490
9491
                                                $link_updates[$my_file_path][] = [
9492
                                                    'orig' => $doc_info[0],
9493
                                                    'dest' => $file_path_dest,
9494
                                                ];
9495
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9496
                                                $my_dep->setAttribute('xml:base', '');
9497
                                            }
9498
                                        } else {
9499
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
9500
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
9501
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
9502
                                        }
9503
                                        break;
9504
                                    default:
9505
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
9506
                                        $my_dep->setAttribute('xml:base', '');
9507
                                        break;
9508
                                }
9509
                            }
9510
                            $my_dep->appendChild($my_dep_file);
9511
                            $resources->appendChild($my_dep);
9512
                            $dependency = $xmldoc->createElement('dependency');
9513
                            $dependency->setAttribute('identifierref', $res_id);
9514
                            $my_resource->appendChild($dependency);
9515
                            $i++;
9516
                        }
9517
                        $resources->appendChild($my_resource);
9518
                        $zip_files[] = $my_file_path;
9519
                        break;
9520
                    default:
9521
                        // Get the path of the file(s) from the course directory root
9522
                        $my_file_path = 'non_exportable.html';
9523
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
9524
                        $my_xml_file_path = $my_file_path;
9525
                        $my_sub_dir = dirname($my_file_path);
9526
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
9527
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
9528
                        $my_xml_sub_dir = $my_sub_dir;
9529
                        // Give a <resource> child to the <resources> element.
9530
                        $my_resource = $xmldoc->createElement('resource');
9531
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
9532
                        $my_resource->setAttribute('type', 'webcontent');
9533
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
9534
                        // adlcp:scormtype can be either 'sco' or 'asset'.
9535
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
9536
                        // xml:base is the base directory to find the files declared in this resource.
9537
                        $my_resource->setAttribute('xml:base', '');
9538
                        // Give a <file> child to the <resource> element.
9539
                        $my_file = $xmldoc->createElement('file');
9540
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
9541
                        $my_resource->appendChild($my_file);
9542
                        $resources->appendChild($my_resource);
9543
                        break;
9544
                }
9545
            }
9546
        }
9547
        $organizations->appendChild($organization);
9548
        $root->appendChild($organizations);
9549
        $root->appendChild($resources);
9550
        $xmldoc->appendChild($root);
9551
9552
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
9553
9554
        // then add the file to the zip, then destroy the file (this is done automatically).
9555
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
9556
        foreach ($zip_files as $file_path) {
9557
            if (empty($file_path)) {
9558
                continue;
9559
            }
9560
9561
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
9562
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
9563
9564
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
9565
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
9566
            }
9567
9568
            $this->create_path($dest_file);
9569
            @copy($filePath, $dest_file);
9570
9571
            // Check if the file needs a link update.
9572
            if (in_array($file_path, array_keys($link_updates))) {
9573
                $string = file_get_contents($dest_file);
9574
                unlink($dest_file);
9575
                foreach ($link_updates[$file_path] as $old_new) {
9576
                    // This is an ugly hack that allows .flv files to be found by the flv player that
9577
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
9578
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
9579
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
9580
                    if ('flv' === substr($old_new['dest'], -3) &&
9581
                        'main/' === substr($old_new['dest'], 0, 5)
9582
                    ) {
9583
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
9584
                    } elseif ('flv' === substr($old_new['dest'], -3) &&
9585
                        'video/' === substr($old_new['dest'], 0, 6)
9586
                    ) {
9587
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
9588
                    }
9589
9590
                    // Fix to avoid problems with default_course_document
9591
                    if (false === strpos('main/default_course_document', $old_new['dest'])) {
9592
                        $newDestination = $old_new['dest'];
9593
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
9594
                            $newDestination = $old_new['replace'];
9595
                        }
9596
                    } else {
9597
                        $newDestination = str_replace('document/', '', $old_new['dest']);
9598
                    }
9599
                    $string = str_replace($old_new['orig'], $newDestination, $string);
9600
9601
                    // Add files inside the HTMLs
9602
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
9603
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
9604
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
9605
                        copy(
9606
                            $sys_course_path.$new_path,
9607
                            $destinationFile
9608
                        );
9609
                    }
9610
                }
9611
                file_put_contents($dest_file, $string);
9612
            }
9613
9614
            if (file_exists($filePath) && $copyAll) {
9615
                $extension = $this->get_extension($filePath);
9616
                if (in_array($extension, ['html', 'html'])) {
9617
                    $containerOrigin = dirname($filePath);
9618
                    $containerDestination = dirname($dest_file);
9619
9620
                    $finder = new Finder();
9621
                    $finder->files()->in($containerOrigin)
9622
                        ->notName('*_DELETED_*')
9623
                        ->exclude('share_folder')
9624
                        ->exclude('chat_files')
9625
                        ->exclude('certificates')
9626
                    ;
9627
9628
                    if (is_dir($containerOrigin) &&
9629
                        is_dir($containerDestination)
9630
                    ) {
9631
                        $fs = new Filesystem();
9632
                        $fs->mirror(
9633
                            $containerOrigin,
9634
                            $containerDestination,
9635
                            $finder
9636
                        );
9637
                    }
9638
                }
9639
            }
9640
        }
9641
9642
        foreach ($zip_files_abs as $file_path) {
9643
            if (empty($file_path)) {
9644
                continue;
9645
            }
9646
9647
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
9648
                continue;
9649
            }
9650
9651
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
9652
            if (false !== strstr($file_path, 'upload/users')) {
9653
                $pos = strpos($file_path, 'my_files/');
9654
                if (false !== $pos) {
9655
                    $onlyDirectory = str_replace(
9656
                        'upload/users/',
9657
                        '',
9658
                        substr($file_path, $pos, strlen($file_path))
9659
                    );
9660
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
9661
                }
9662
            }
9663
9664
            if (false !== strstr($file_path, 'default_course_document/')) {
9665
                $replace = str_replace('/main', '', $file_path);
9666
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
9667
            }
9668
9669
            if (empty($dest_file)) {
9670
                continue;
9671
            }
9672
9673
            $this->create_path($dest_file);
9674
            copy($main_path.$file_path, $dest_file);
9675
            // Check if the file needs a link update.
9676
            if (in_array($file_path, array_keys($link_updates))) {
9677
                $string = file_get_contents($dest_file);
9678
                unlink($dest_file);
9679
                foreach ($link_updates[$file_path] as $old_new) {
9680
                    // This is an ugly hack that allows .flv files to be found by the flv player that
9681
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
9682
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
9683
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
9684
                    if ('flv' == substr($old_new['dest'], -3) &&
9685
                        'main/' == substr($old_new['dest'], 0, 5)
9686
                    ) {
9687
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
9688
                    }
9689
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
9690
                }
9691
                file_put_contents($dest_file, $string);
9692
            }
9693
        }
9694
9695
        if (is_array($links_to_create)) {
9696
            foreach ($links_to_create as $file => $link) {
9697
                $content = '<!DOCTYPE html><head>
9698
                            <meta charset="'.api_get_language_isocode().'" />
9699
                            <title>'.$link['title'].'</title>
9700
                            </head>
9701
                            <body dir="'.api_get_text_direction().'">
9702
                            <div style="text-align:center">
9703
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
9704
                            </body>
9705
                            </html>';
9706
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
9707
            }
9708
        }
9709
9710
        // Add non exportable message explanation.
9711
        $lang_not_exportable = get_lang('This learning object or activity is not SCORM compliant. That\'s why it is not exportable.');
9712
        $file_content = '<!DOCTYPE html><head>
9713
                        <meta charset="'.api_get_language_isocode().'" />
9714
                        <title>'.$lang_not_exportable.'</title>
9715
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
9716
                        </head>
9717
                        <body dir="'.api_get_text_direction().'">';
9718
        $file_content .=
9719
            <<<EOD
9720
                    <style>
9721
            .error-message {
9722
                font-family: arial, verdana, helvetica, sans-serif;
9723
                border-width: 1px;
9724
                border-style: solid;
9725
                left: 50%;
9726
                margin: 10px auto;
9727
                min-height: 30px;
9728
                padding: 5px;
9729
                right: 50%;
9730
                width: 500px;
9731
                background-color: #FFD1D1;
9732
                border-color: #FF0000;
9733
                color: #000;
9734
            }
9735
        </style>
9736
    <body>
9737
        <div class="error-message">
9738
            $lang_not_exportable
9739
        </div>
9740
    </body>
9741
</html>
9742
EOD;
9743
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
9744
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
9745
        }
9746
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
9747
9748
        // Add the extra files that go along with a SCORM package.
9749
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
9750
9751
        $fs = new Filesystem();
9752
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
9753
9754
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
9755
        $manifest = @$xmldoc->saveXML();
9756
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
9757
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
9758
        $zip_folder->add(
9759
            $archivePath.'/'.$temp_dir_short,
9760
            PCLZIP_OPT_REMOVE_PATH,
9761
            $archivePath.'/'.$temp_dir_short.'/'
9762
        );
9763
9764
        // Clean possible temporary files.
9765
        foreach ($files_cleanup as $file) {
9766
            $res = unlink($file);
9767
            if (false === $res) {
9768
                error_log(
9769
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
9770
                    0
9771
                );
9772
            }
9773
        }
9774
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
9775
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
9776
    }
9777
9778
    /**
9779
     * @param int $lp_id
9780
     *
9781
     * @return bool
9782
     */
9783
    public function scorm_export_to_pdf($lp_id)
9784
    {
9785
        $lp_id = (int) $lp_id;
9786
        $files_to_export = [];
9787
9788
        $sessionId = api_get_session_id();
9789
        $course_data = api_get_course_info($this->cc);
9790
9791
        if (!empty($course_data)) {
9792
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
9793
            $list = self::get_flat_ordered_items_list($lp_id);
9794
            if (!empty($list)) {
9795
                foreach ($list as $item_id) {
9796
                    $item = $this->items[$item_id];
9797
                    switch ($item->type) {
9798
                        case 'document':
9799
                            // Getting documents from a LP with chamilo documents
9800
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
9801
                            // Try loading document from the base course.
9802
                            if (empty($file_data) && !empty($sessionId)) {
9803
                                $file_data = DocumentManager::get_document_data_by_id(
9804
                                    $item->path,
9805
                                    $this->cc,
9806
                                    false,
9807
                                    0
9808
                                );
9809
                            }
9810
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
9811
                            if (file_exists($file_path)) {
9812
                                $files_to_export[] = [
9813
                                    'title' => $item->get_title(),
9814
                                    'path' => $file_path,
9815
                                ];
9816
                            }
9817
                            break;
9818
                        case 'asset': //commes from a scorm package generated by chamilo
9819
                        case 'sco':
9820
                            $file_path = $scorm_path.'/'.$item->path;
9821
                            if (file_exists($file_path)) {
9822
                                $files_to_export[] = [
9823
                                    'title' => $item->get_title(),
9824
                                    'path' => $file_path,
9825
                                ];
9826
                            }
9827
                            break;
9828
                        case 'dir':
9829
                            $files_to_export[] = [
9830
                                'title' => $item->get_title(),
9831
                                'path' => null,
9832
                            ];
9833
                            break;
9834
                    }
9835
                }
9836
            }
9837
9838
            $pdf = new PDF();
9839
            $result = $pdf->html_to_pdf(
9840
                $files_to_export,
9841
                $this->name,
9842
                $this->cc,
9843
                true,
9844
                true,
9845
                true,
9846
                $this->get_name()
9847
            );
9848
9849
            return $result;
9850
        }
9851
9852
        return false;
9853
    }
9854
9855
    /**
9856
     * Temp function to be moved in main_api or the best place around for this.
9857
     * Creates a file path if it doesn't exist.
9858
     *
9859
     * @param string $path
9860
     */
9861
    public function create_path($path)
9862
    {
9863
        $path_bits = explode('/', dirname($path));
9864
9865
        // IS_WINDOWS_OS has been defined in main_api.lib.php
9866
        $path_built = IS_WINDOWS_OS ? '' : '/';
9867
        foreach ($path_bits as $bit) {
9868
            if (!empty($bit)) {
9869
                $new_path = $path_built.$bit;
9870
                if (is_dir($new_path)) {
9871
                    $path_built = $new_path.'/';
9872
                } else {
9873
                    mkdir($new_path, api_get_permissions_for_new_directories());
9874
                    $path_built = $new_path.'/';
9875
                }
9876
            }
9877
        }
9878
    }
9879
9880
    /**
9881
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
9882
     *
9883
     * @return bool The results of the unlink function, or false if there was no image to start with
9884
     */
9885
    public function delete_lp_image()
9886
    {
9887
        $img = $this->get_preview_image();
9888
        if ('' != $img) {
9889
            $del_file = $this->get_preview_image_path(null, 'sys');
9890
            if (isset($del_file) && file_exists($del_file)) {
9891
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
9892
                if (file_exists($del_file_2)) {
9893
                    unlink($del_file_2);
9894
                }
9895
                $this->set_preview_image('');
9896
9897
                return @unlink($del_file);
9898
            }
9899
        }
9900
9901
        return false;
9902
    }
9903
9904
    /**
9905
     * Uploads an author image to the upload/learning_path/images directory.
9906
     *
9907
     * @param array    The image array, coming from the $_FILES superglobal
9908
     *
9909
     * @return bool True on success, false on error
9910
     */
9911
    public function upload_image($image_array)
9912
    {
9913
        if (!empty($image_array['name'])) {
9914
            $upload_ok = process_uploaded_file($image_array);
9915
            $has_attachment = true;
9916
        }
9917
9918
        if ($upload_ok && $has_attachment) {
9919
            $courseDir = api_get_course_path().'/upload/learning_path/images';
9920
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
9921
            $updir = $sys_course_path.$courseDir;
9922
            // Try to add an extension to the file if it hasn't one.
9923
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
9924
9925
            if (filter_extension($new_file_name)) {
9926
                $file_extension = explode('.', $image_array['name']);
9927
                $file_extension = strtolower($file_extension[count($file_extension) - 1]);
9928
                $filename = uniqid('');
9929
                $new_file_name = $filename.'.'.$file_extension;
9930
                $new_path = $updir.'/'.$new_file_name;
9931
9932
                // Resize the image.
9933
                $temp = new Image($image_array['tmp_name']);
9934
                $temp->resize(104);
9935
                $result = $temp->send_image($new_path);
9936
9937
                // Storing the image filename.
9938
                if ($result) {
9939
                    $this->set_preview_image($new_file_name);
9940
9941
                    //Resize to 64px to use on course homepage
9942
                    $temp->resize(64);
9943
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
9944
9945
                    return true;
9946
                }
9947
            }
9948
        }
9949
9950
        return false;
9951
    }
9952
9953
    /**
9954
     * @param int    $lp_id
9955
     * @param string $status
9956
     */
9957
    public function set_autolaunch($lp_id, $status)
9958
    {
9959
        $course_id = api_get_course_int_id();
9960
        $lp_id = (int) $lp_id;
9961
        $status = (int) $status;
9962
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
9963
9964
        // Setting everything to autolaunch = 0
9965
        $attributes['autolaunch'] = 0;
9966
        $where = [
9967
            'session_id = ? AND c_id = ? ' => [
9968
                api_get_session_id(),
9969
                $course_id,
9970
            ],
9971
        ];
9972
        Database::update($lp_table, $attributes, $where);
9973
        if (1 == $status) {
9974
            //Setting my lp_id to autolaunch = 1
9975
            $attributes['autolaunch'] = 1;
9976
            $where = [
9977
                'iid = ? AND session_id = ? AND c_id = ?' => [
9978
                    $lp_id,
9979
                    api_get_session_id(),
9980
                    $course_id,
9981
                ],
9982
            ];
9983
            Database::update($lp_table, $attributes, $where);
9984
        }
9985
    }
9986
9987
    /**
9988
     * Gets previous_item_id for the next element of the lp_item table.
9989
     *
9990
     * @author Isaac flores paz
9991
     *
9992
     * @return int Previous item ID
9993
     */
9994
    public function select_previous_item_id()
9995
    {
9996
        $course_id = api_get_course_int_id();
9997
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9998
9999
        // Get the max order of the items
10000
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
10001
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
10002
        $rs_max_order = Database::query($sql);
10003
        $row_max_order = Database::fetch_object($rs_max_order);
10004
        $max_order = $row_max_order->display_order;
10005
        // Get the previous item ID
10006
        $sql = "SELECT iid as previous FROM $table_lp_item
10007
                WHERE
10008
                    c_id = $course_id AND
10009
                    lp_id = ".$this->lp_id." AND
10010
                    display_order = '$max_order' ";
10011
        $rs_max = Database::query($sql);
10012
        $row_max = Database::fetch_object($rs_max);
10013
10014
        // Return the previous item ID
10015
        return $row_max->previous;
10016
    }
10017
10018
    /**
10019
     * Copies an LP.
10020
     */
10021
    public function copy()
10022
    {
10023
        // Course builder
10024
        $cb = new CourseBuilder();
10025
10026
        //Setting tools that will be copied
10027
        $cb->set_tools_to_build(['learnpaths']);
10028
10029
        //Setting elements that will be copied
10030
        $cb->set_tools_specific_id_list(
10031
            ['learnpaths' => [$this->lp_id]]
10032
        );
10033
10034
        $course = $cb->build();
10035
10036
        //Course restorer
10037
        $course_restorer = new CourseRestorer($course);
10038
        $course_restorer->set_add_text_in_items(true);
10039
        $course_restorer->set_tool_copy_settings(
10040
            ['learnpaths' => ['reset_dates' => true]]
10041
        );
10042
        $course_restorer->restore(
10043
            api_get_course_id(),
10044
            api_get_session_id(),
10045
            false,
10046
            false
10047
        );
10048
    }
10049
10050
    /**
10051
     * Verify document size.
10052
     *
10053
     * @param string $s
10054
     *
10055
     * @return bool
10056
     */
10057
    public static function verify_document_size($s)
10058
    {
10059
        $post_max = ini_get('post_max_size');
10060
        if ('M' == substr($post_max, -1, 1)) {
10061
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
10062
        } elseif ('G' == substr($post_max, -1, 1)) {
10063
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
10064
        }
10065
        $upl_max = ini_get('upload_max_filesize');
10066
        if ('M' == substr($upl_max, -1, 1)) {
10067
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
10068
        } elseif ('G' == substr($upl_max, -1, 1)) {
10069
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
10070
        }
10071
10072
        $repo = Container::getDocumentRepository();
10073
        $documents_total_space = $repo->getTotalSpace(api_get_course_int_id());
10074
10075
        $course_max_space = DocumentManager::get_course_quota();
10076
        $total_size = filesize($s) + $documents_total_space;
10077
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
10078
            return true;
10079
        }
10080
10081
        return false;
10082
    }
10083
10084
    /**
10085
     * Clear LP prerequisites.
10086
     */
10087
    public function clear_prerequisites()
10088
    {
10089
        $course_id = $this->get_course_int_id();
10090
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10091
        $lp_id = $this->get_id();
10092
        // Cleaning prerequisites
10093
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
10094
                WHERE c_id = $course_id AND lp_id = $lp_id";
10095
        Database::query($sql);
10096
10097
        // Cleaning mastery score for exercises
10098
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
10099
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
10100
        Database::query($sql);
10101
    }
10102
10103
    public function set_previous_step_as_prerequisite_for_all_items()
10104
    {
10105
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10106
        $course_id = $this->get_course_int_id();
10107
        $lp_id = $this->get_id();
10108
10109
        if (!empty($this->items)) {
10110
            $previous_item_id = null;
10111
            $previous_item_max = 0;
10112
            $previous_item_type = null;
10113
            $last_item_not_dir = null;
10114
            $last_item_not_dir_type = null;
10115
            $last_item_not_dir_max = null;
10116
10117
            foreach ($this->ordered_items as $itemId) {
10118
                $item = $this->getItem($itemId);
10119
                // if there was a previous item... (otherwise jump to set it)
10120
                if (!empty($previous_item_id)) {
10121
                    $current_item_id = $item->get_id(); //save current id
10122
                    if ('dir' != $item->get_type()) {
10123
                        // Current item is not a folder, so it qualifies to get a prerequisites
10124
                        if ('quiz' == $last_item_not_dir_type) {
10125
                            // if previous is quiz, mark its max score as default score to be achieved
10126
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
10127
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
10128
                            Database::query($sql);
10129
                        }
10130
                        // now simply update the prerequisite to set it to the last non-chapter item
10131
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
10132
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
10133
                        Database::query($sql);
10134
                        // record item as 'non-chapter' reference
10135
                        $last_item_not_dir = $item->get_id();
10136
                        $last_item_not_dir_type = $item->get_type();
10137
                        $last_item_not_dir_max = $item->get_max();
10138
                    }
10139
                } else {
10140
                    if ('dir' != $item->get_type()) {
10141
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
10142
                        $last_item_not_dir = $item->get_id();
10143
                        $last_item_not_dir_type = $item->get_type();
10144
                        $last_item_not_dir_max = $item->get_max();
10145
                    }
10146
                }
10147
                // Saving the item as "previous item" for the next loop
10148
                $previous_item_id = $item->get_id();
10149
                $previous_item_max = $item->get_max();
10150
                $previous_item_type = $item->get_type();
10151
            }
10152
        }
10153
    }
10154
10155
    /**
10156
     * @param array $params
10157
     *
10158
     * @return int
10159
     */
10160
    public static function createCategory($params)
10161
    {
10162
        $courseEntity = api_get_course_entity(api_get_course_int_id());
10163
10164
        $item = new CLpCategory();
10165
        $item
10166
            ->setName($params['name'])
10167
            ->setCId($params['c_id'])
10168
            ->setParent($courseEntity)
10169
            ->addCourseLink($courseEntity, api_get_session_entity())
10170
        ;
10171
10172
        $repo = Container::getLpCategoryRepository();
10173
        $repo->create($item);
10174
10175
        /*api_item_property_update(
10176
            api_get_course_info(),
10177
            TOOL_LEARNPATH_CATEGORY,
10178
            $item->getId(),
10179
            'visible',
10180
            api_get_user_id()
10181
        );*/
10182
10183
        return $item->getIid();
10184
    }
10185
10186
    /**
10187
     * @param array $params
10188
     */
10189
    public static function updateCategory($params)
10190
    {
10191
        $em = Database::getManager();
10192
        /** @var CLpCategory $item */
10193
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
10194
        if ($item) {
10195
            $item->setName($params['name']);
10196
            $em->persist($item);
10197
            $em->flush();
10198
        }
10199
    }
10200
10201
    /**
10202
     * @param int $id
10203
     */
10204
    public static function moveUpCategory($id)
10205
    {
10206
        $id = (int) $id;
10207
        $em = Database::getManager();
10208
        /** @var CLpCategory $item */
10209
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
10210
        if ($item) {
10211
            $position = $item->getPosition() - 1;
10212
            $item->setPosition($position);
10213
            $em->persist($item);
10214
            $em->flush();
10215
        }
10216
    }
10217
10218
    /**
10219
     * @param int $id
10220
     */
10221
    public static function moveDownCategory($id)
10222
    {
10223
        $id = (int) $id;
10224
        $em = Database::getManager();
10225
        /** @var CLpCategory $item */
10226
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
10227
        if ($item) {
10228
            $position = $item->getPosition() + 1;
10229
            $item->setPosition($position);
10230
            $em->persist($item);
10231
            $em->flush();
10232
        }
10233
    }
10234
10235
    public static function getLpList($courseId)
10236
    {
10237
        $table = Database::get_course_table(TABLE_LP_MAIN);
10238
        $courseId = (int) $courseId;
10239
10240
        $sql = "SELECT * FROM $table WHERE c_id = $courseId";
10241
        $result = Database::query($sql);
10242
10243
        return Database::store_result($result, 'ASSOC');
10244
    }
10245
10246
    /**
10247
     * @param int $courseId
10248
     *
10249
     * @throws \Doctrine\ORM\Query\QueryException
10250
     *
10251
     * @return int|mixed
10252
     */
10253
    public static function getCountCategories($courseId)
10254
    {
10255
        if (empty($courseId)) {
10256
            return 0;
10257
        }
10258
        $em = Database::getManager();
10259
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
10260
        $query->setParameter('id', $courseId);
10261
10262
        return $query->getSingleScalarResult();
10263
    }
10264
10265
    /**
10266
     * @param int $courseId
10267
     *
10268
     * @return CLpCategory[]
10269
     */
10270
    public static function getCategories($courseId)
10271
    {
10272
        $em = Database::getManager();
10273
10274
        // Using doctrine extensions
10275
        /** @var SortableRepository $repo */
10276
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
10277
10278
        return $repo->getBySortableGroupsQuery(['cId' => $courseId])->getResult();
10279
    }
10280
10281
    public static function getCategorySessionId($id)
10282
    {
10283
        if (false === api_get_configuration_value('allow_session_lp_category')) {
10284
            return 0;
10285
        }
10286
10287
        $table = Database::get_course_table(TABLE_LP_CATEGORY);
10288
        $id = (int) $id;
10289
10290
        $sql = "SELECT session_id FROM $table WHERE iid = $id";
10291
        $result = Database::query($sql);
10292
        $result = Database::fetch_array($result, 'ASSOC');
10293
10294
        if ($result) {
10295
            return (int) $result['session_id'];
10296
        }
10297
10298
        return 0;
10299
    }
10300
10301
    /**
10302
     * @param int $id
10303
     *
10304
     * @return CLpCategory
10305
     */
10306
    public static function getCategory($id)
10307
    {
10308
        $id = (int) $id;
10309
        $em = Database::getManager();
10310
10311
        return $em->find('ChamiloCourseBundle:CLpCategory', $id);
10312
    }
10313
10314
    /**
10315
     * @param int $courseId
10316
     *
10317
     * @return array
10318
     */
10319
    public static function getCategoryByCourse($courseId)
10320
    {
10321
        $em = Database::getManager();
10322
        $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
10323
            ['cId' => $courseId]
10324
        );
10325
10326
        return $items;
10327
    }
10328
10329
    /**
10330
     * @param int $id
10331
     */
10332
    public static function deleteCategory($id): bool
10333
    {
10334
        $repo = Container::getLpCategoryRepository();
10335
        /** @var CLpCategory $category */
10336
        $category = $repo->find($id);
10337
        if ($category) {
10338
            $em = Database::getManager();
10339
            $lps = $category->getLps();
10340
10341
            foreach ($lps as $lp) {
10342
                $lp->setCategory(null);
10343
                $em->persist($lp);
10344
            }
10345
10346
            // Removing category.
10347
            $em->remove($category);
10348
            $em->flush();
10349
10350
            return true;
10351
        }
10352
10353
        return false;
10354
    }
10355
10356
    /**
10357
     * @param int  $courseId
10358
     * @param bool $addSelectOption
10359
     *
10360
     * @return mixed
10361
     */
10362
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
10363
    {
10364
        $items = self::getCategoryByCourse($courseId);
10365
        $cats = [];
10366
        if ($addSelectOption) {
10367
            $cats = [get_lang('Select a category')];
10368
        }
10369
10370
        if (!empty($items)) {
10371
            foreach ($items as $cat) {
10372
                $cats[$cat->getIid()] = $cat->getName();
10373
            }
10374
        }
10375
10376
        return $cats;
10377
    }
10378
10379
    /**
10380
     * @param string $courseCode
10381
     * @param int    $lpId
10382
     * @param int    $user_id
10383
     *
10384
     * @return learnpath
10385
     */
10386
    public static function getLpFromSession($courseCode, $lpId, $user_id)
10387
    {
10388
        $debug = 0;
10389
        $learnPath = null;
10390
        $lpObject = Session::read('lpobject');
10391
        if (null !== $lpObject) {
10392
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
10393
            if ($debug) {
10394
                error_log('getLpFromSession: unserialize');
10395
                error_log('------getLpFromSession------');
10396
                error_log('------unserialize------');
10397
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
10398
                error_log("api_get_sessionid: ".api_get_session_id());
10399
            }
10400
        }
10401
10402
        if (!is_object($learnPath)) {
10403
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
10404
            if ($debug) {
10405
                error_log('------getLpFromSession------');
10406
                error_log('getLpFromSession: create new learnpath');
10407
                error_log("create new LP with $courseCode - $lpId - $user_id");
10408
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
10409
                error_log("api_get_sessionid: ".api_get_session_id());
10410
            }
10411
        }
10412
10413
        return $learnPath;
10414
    }
10415
10416
    /**
10417
     * @param int $itemId
10418
     *
10419
     * @return learnpathItem|false
10420
     */
10421
    public function getItem($itemId)
10422
    {
10423
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
10424
            return $this->items[$itemId];
10425
        }
10426
10427
        return false;
10428
    }
10429
10430
    /**
10431
     * @return int
10432
     */
10433
    public function getCurrentAttempt()
10434
    {
10435
        $attempt = $this->getItem($this->get_current_item_id());
10436
        if ($attempt) {
10437
            return $attempt->get_attempt_id();
10438
        }
10439
10440
        return 0;
10441
    }
10442
10443
    /**
10444
     * @return int
10445
     */
10446
    public function getCategoryId()
10447
    {
10448
        return (int) $this->categoryId;
10449
    }
10450
10451
    /**
10452
     * @param int $categoryId
10453
     *
10454
     * @return bool
10455
     */
10456
    public function setCategoryId($categoryId)
10457
    {
10458
        $this->categoryId = (int) $categoryId;
10459
        $table = Database::get_course_table(TABLE_LP_MAIN);
10460
        $lp_id = $this->get_id();
10461
        if (empty($categoryId)) {
10462
            $this->categoryId = null;
10463
            $sql = "UPDATE $table SET category_id = NULL WHERE iid = $lp_id";
10464
            Database::query($sql);
10465
10466
            return true;
10467
        }
10468
10469
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
10470
                WHERE iid = $lp_id";
10471
        Database::query($sql);
10472
10473
        return true;
10474
    }
10475
10476
    /**
10477
     * Get whether this is a learning path with the possibility to subscribe
10478
     * users or not.
10479
     *
10480
     * @return int
10481
     */
10482
    public function getSubscribeUsers()
10483
    {
10484
        return $this->subscribeUsers;
10485
    }
10486
10487
    /**
10488
     * Set whether this is a learning path with the possibility to subscribe
10489
     * users or not.
10490
     *
10491
     * @param int $value (0 = false, 1 = true)
10492
     *
10493
     * @return bool
10494
     */
10495
    public function setSubscribeUsers($value)
10496
    {
10497
        $this->subscribeUsers = (int) $value;
10498
        $table = Database::get_course_table(TABLE_LP_MAIN);
10499
        $lp_id = $this->get_id();
10500
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
10501
                WHERE iid = $lp_id";
10502
        Database::query($sql);
10503
10504
        return true;
10505
    }
10506
10507
    /**
10508
     * Calculate the count of stars for a user in this LP
10509
     * This calculation is based on the following rules:
10510
     * - the student gets one star when he gets to 50% of the learning path
10511
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
10512
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
10513
     * - the student gets the final star when the score for the *last* test is >= 80%.
10514
     *
10515
     * @param int $sessionId Optional. The session ID
10516
     *
10517
     * @return int The count of stars
10518
     */
10519
    public function getCalculateStars($sessionId = 0)
10520
    {
10521
        $stars = 0;
10522
        $progress = self::getProgress(
10523
            $this->lp_id,
10524
            $this->user_id,
10525
            $this->course_int_id,
10526
            $sessionId
10527
        );
10528
10529
        if ($progress >= 50) {
10530
            $stars++;
10531
        }
10532
10533
        // Calculate stars chapters evaluation
10534
        $exercisesItems = $this->getExercisesItems();
10535
10536
        if (!empty($exercisesItems)) {
10537
            $totalResult = 0;
10538
10539
            foreach ($exercisesItems as $exerciseItem) {
10540
                $exerciseResultInfo = Event::getExerciseResultsByUser(
10541
                    $this->user_id,
10542
                    $exerciseItem->path,
10543
                    $this->course_int_id,
10544
                    $sessionId,
10545
                    $this->lp_id,
10546
                    $exerciseItem->db_id
10547
                );
10548
10549
                $exerciseResultInfo = end($exerciseResultInfo);
10550
10551
                if (!$exerciseResultInfo) {
10552
                    continue;
10553
                }
10554
10555
                if (!empty($exerciseResultInfo['max_score'])) {
10556
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
10557
                } else {
10558
                    $exerciseResult = 0;
10559
                }
10560
                $totalResult += $exerciseResult;
10561
            }
10562
10563
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
10564
10565
            if ($totalExerciseAverage >= 50) {
10566
                $stars++;
10567
            }
10568
10569
            if ($totalExerciseAverage >= 80) {
10570
                $stars++;
10571
            }
10572
        }
10573
10574
        // Calculate star for final evaluation
10575
        $finalEvaluationItem = $this->getFinalEvaluationItem();
10576
10577
        if (!empty($finalEvaluationItem)) {
10578
            $evaluationResultInfo = Event::getExerciseResultsByUser(
10579
                $this->user_id,
10580
                $finalEvaluationItem->path,
10581
                $this->course_int_id,
10582
                $sessionId,
10583
                $this->lp_id,
10584
                $finalEvaluationItem->db_id
10585
            );
10586
10587
            $evaluationResultInfo = end($evaluationResultInfo);
10588
10589
            if ($evaluationResultInfo) {
10590
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
10591
10592
                if ($evaluationResult >= 80) {
10593
                    $stars++;
10594
                }
10595
            }
10596
        }
10597
10598
        return $stars;
10599
    }
10600
10601
    /**
10602
     * Get the items of exercise type.
10603
     *
10604
     * @return array The items. Otherwise return false
10605
     */
10606
    public function getExercisesItems()
10607
    {
10608
        $exercises = [];
10609
        foreach ($this->items as $item) {
10610
            if ('quiz' != $item->type) {
10611
                continue;
10612
            }
10613
            $exercises[] = $item;
10614
        }
10615
10616
        array_pop($exercises);
10617
10618
        return $exercises;
10619
    }
10620
10621
    /**
10622
     * Get the item of exercise type (evaluation type).
10623
     *
10624
     * @return array The final evaluation. Otherwise return false
10625
     */
10626
    public function getFinalEvaluationItem()
10627
    {
10628
        $exercises = [];
10629
        foreach ($this->items as $item) {
10630
            if (TOOL_QUIZ !== $item->type) {
10631
                continue;
10632
            }
10633
10634
            $exercises[] = $item;
10635
        }
10636
10637
        return array_pop($exercises);
10638
    }
10639
10640
    /**
10641
     * Calculate the total points achieved for the current user in this learning path.
10642
     *
10643
     * @param int $sessionId Optional. The session Id
10644
     *
10645
     * @return int
10646
     */
10647
    public function getCalculateScore($sessionId = 0)
10648
    {
10649
        // Calculate stars chapters evaluation
10650
        $exercisesItems = $this->getExercisesItems();
10651
        $finalEvaluationItem = $this->getFinalEvaluationItem();
10652
        $totalExercisesResult = 0;
10653
        $totalEvaluationResult = 0;
10654
10655
        if (false !== $exercisesItems) {
10656
            foreach ($exercisesItems as $exerciseItem) {
10657
                $exerciseResultInfo = Event::getExerciseResultsByUser(
10658
                    $this->user_id,
10659
                    $exerciseItem->path,
10660
                    $this->course_int_id,
10661
                    $sessionId,
10662
                    $this->lp_id,
10663
                    $exerciseItem->db_id
10664
                );
10665
10666
                $exerciseResultInfo = end($exerciseResultInfo);
10667
10668
                if (!$exerciseResultInfo) {
10669
                    continue;
10670
                }
10671
10672
                $totalExercisesResult += $exerciseResultInfo['score'];
10673
            }
10674
        }
10675
10676
        if (!empty($finalEvaluationItem)) {
10677
            $evaluationResultInfo = Event::getExerciseResultsByUser(
10678
                $this->user_id,
10679
                $finalEvaluationItem->path,
10680
                $this->course_int_id,
10681
                $sessionId,
10682
                $this->lp_id,
10683
                $finalEvaluationItem->db_id
10684
            );
10685
10686
            $evaluationResultInfo = end($evaluationResultInfo);
10687
10688
            if ($evaluationResultInfo) {
10689
                $totalEvaluationResult += $evaluationResultInfo['score'];
10690
            }
10691
        }
10692
10693
        return $totalExercisesResult + $totalEvaluationResult;
10694
    }
10695
10696
    /**
10697
     * Check if URL is not allowed to be show in a iframe.
10698
     *
10699
     * @param string $src
10700
     *
10701
     * @return string
10702
     */
10703
    public function fixBlockedLinks($src)
10704
    {
10705
        $urlInfo = parse_url($src);
10706
10707
        $platformProtocol = 'https';
10708
        if (false === strpos(api_get_path(WEB_CODE_PATH), 'https')) {
10709
            $platformProtocol = 'http';
10710
        }
10711
10712
        $protocolFixApplied = false;
10713
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
10714
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
10715
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
10716
10717
        if ($platformProtocol != $scheme) {
10718
            Session::write('x_frame_source', $src);
10719
            $src = 'blank.php?error=x_frames_options';
10720
            $protocolFixApplied = true;
10721
        }
10722
10723
        if (false == $protocolFixApplied) {
10724
            if (false === strpos(api_get_path(WEB_PATH), $host)) {
10725
                // Check X-Frame-Options
10726
                $ch = curl_init();
10727
                $options = [
10728
                    CURLOPT_URL => $src,
10729
                    CURLOPT_RETURNTRANSFER => true,
10730
                    CURLOPT_HEADER => true,
10731
                    CURLOPT_FOLLOWLOCATION => true,
10732
                    CURLOPT_ENCODING => "",
10733
                    CURLOPT_AUTOREFERER => true,
10734
                    CURLOPT_CONNECTTIMEOUT => 120,
10735
                    CURLOPT_TIMEOUT => 120,
10736
                    CURLOPT_MAXREDIRS => 10,
10737
                ];
10738
10739
                $proxySettings = api_get_configuration_value('proxy_settings');
10740
                if (!empty($proxySettings) &&
10741
                    isset($proxySettings['curl_setopt_array'])
10742
                ) {
10743
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
10744
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
10745
                }
10746
10747
                curl_setopt_array($ch, $options);
10748
                $response = curl_exec($ch);
10749
                $httpCode = curl_getinfo($ch);
10750
                $headers = substr($response, 0, $httpCode['header_size']);
10751
10752
                $error = false;
10753
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
10754
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
10755
                ) {
10756
                    $error = true;
10757
                }
10758
10759
                if ($error) {
10760
                    Session::write('x_frame_source', $src);
10761
                    $src = 'blank.php?error=x_frames_options';
10762
                }
10763
            }
10764
        }
10765
10766
        return $src;
10767
    }
10768
10769
    /**
10770
     * Check if this LP has a created forum in the basis course.
10771
     *
10772
     * @return bool
10773
     */
10774
    public function lpHasForum()
10775
    {
10776
        $forumTable = Database::get_course_table(TABLE_FORUM);
10777
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
10778
10779
        $fakeFrom = "
10780
            $forumTable f
10781
            INNER JOIN $itemProperty ip
10782
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
10783
        ";
10784
10785
        $resultData = Database::select(
10786
            'COUNT(f.iid) AS qty',
10787
            $fakeFrom,
10788
            [
10789
                'where' => [
10790
                    'ip.visibility != ? AND ' => 2,
10791
                    'ip.tool = ? AND ' => TOOL_FORUM,
10792
                    'f.c_id = ? AND ' => intval($this->course_int_id),
10793
                    'f.lp_id = ?' => intval($this->lp_id),
10794
                ],
10795
            ],
10796
            'first'
10797
        );
10798
10799
        return $resultData['qty'] > 0;
10800
    }
10801
10802
    /**
10803
     * Get the forum for this learning path.
10804
     *
10805
     * @param int $sessionId
10806
     *
10807
     * @return array
10808
     */
10809
    public function getForum($sessionId = 0)
10810
    {
10811
        $repo = Container::getForumRepository();
10812
10813
        $course = api_get_course_entity();
10814
        $session = api_get_session_entity($sessionId);
10815
        $qb = $repo->getResourcesByCourse($course, $session);
10816
10817
        return $qb->getQuery()->getResult();
10818
    }
10819
10820
    /**
10821
     * Create a forum for this learning path.
10822
     *
10823
     * @param int $forumCategoryId
10824
     *
10825
     * @return int The forum ID if was created. Otherwise return false
10826
     */
10827
    public function createForum($forumCategoryId)
10828
    {
10829
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
10830
10831
        return store_forum(
10832
            [
10833
                'lp_id' => $this->lp_id,
10834
                'forum_title' => $this->name,
10835
                'forum_comment' => null,
10836
                'forum_category' => (int) $forumCategoryId,
10837
                'students_can_edit_group' => ['students_can_edit' => 0],
10838
                'allow_new_threads_group' => ['allow_new_threads' => 0],
10839
                'default_view_type_group' => ['default_view_type' => 'flat'],
10840
                'group_forum' => 0,
10841
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
10842
            ],
10843
            [],
10844
            true
10845
        );
10846
    }
10847
10848
    /**
10849
     * Get the LP Final Item form.
10850
     *
10851
     * @throws Exception
10852
     * @throws HTML_QuickForm_Error
10853
     *
10854
     * @return string
10855
     */
10856
    public function getFinalItemForm()
10857
    {
10858
        $finalItem = $this->getFinalItem();
10859
        $title = '';
10860
10861
        if ($finalItem) {
10862
            $title = $finalItem->get_title();
10863
            $buttonText = get_lang('Save');
10864
            $content = $this->getSavedFinalItem();
10865
        } else {
10866
            $buttonText = get_lang('Add this document to the course');
10867
            $content = $this->getFinalItemTemplate();
10868
        }
10869
10870
        $editorConfig = [
10871
            'ToolbarSet' => 'LearningPathDocuments',
10872
            'Width' => '100%',
10873
            'Height' => '500',
10874
            'FullPage' => true,
10875
//            'CreateDocumentDir' => $relative_prefix,
10876
    //            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
10877
  //          'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
10878
        ];
10879
10880
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
10881
            'type' => 'document',
10882
            'lp_id' => $this->lp_id,
10883
        ]);
10884
10885
        $form = new FormValidator('final_item', 'POST', $url);
10886
        $form->addText('title', get_lang('Title'));
10887
        $form->addButtonSave($buttonText);
10888
        $form->addHtml(
10889
            Display::return_message(
10890
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
10891
                'normal',
10892
                false
10893
            )
10894
        );
10895
10896
        $renderer = $form->defaultRenderer();
10897
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
10898
10899
        $form->addHtmlEditor(
10900
            'content_lp_certificate',
10901
            null,
10902
            true,
10903
            false,
10904
            $editorConfig,
10905
            true
10906
        );
10907
        $form->addHidden('action', 'add_final_item');
10908
        $form->addHidden('path', Session::read('pathItem'));
10909
        $form->addHidden('previous', $this->get_last());
10910
        $form->setDefaults(
10911
            ['title' => $title, 'content_lp_certificate' => $content]
10912
        );
10913
10914
        if ($form->validate()) {
10915
            $values = $form->exportValues();
10916
            $lastItemId = $this->getLastInFirstLevel();
10917
10918
            if (!$finalItem) {
10919
                $documentId = $this->create_document(
10920
                    $this->course_info,
10921
                    $values['content_lp_certificate'],
10922
                    $values['title']
10923
                );
10924
                $this->add_item(
10925
                    0,
10926
                    $lastItemId,
10927
                    'final_item',
10928
                    $documentId,
10929
                    $values['title'],
10930
                    ''
10931
                );
10932
10933
                Display::addFlash(
10934
                    Display::return_message(get_lang('Added'))
10935
                );
10936
            } else {
10937
                $this->edit_document($this->course_info);
10938
            }
10939
        }
10940
10941
        return $form->returnForm();
10942
    }
10943
10944
    /**
10945
     * Check if the current lp item is first, both, last or none from lp list.
10946
     *
10947
     * @param int $currentItemId
10948
     *
10949
     * @return string
10950
     */
10951
    public function isFirstOrLastItem($currentItemId)
10952
    {
10953
        $lpItemId = [];
10954
        $typeListNotToVerify = self::getChapterTypes();
10955
10956
        // Using get_toc() function instead $this->items because returns the correct order of the items
10957
        foreach ($this->get_toc() as $item) {
10958
            if (!in_array($item['type'], $typeListNotToVerify)) {
10959
                $lpItemId[] = $item['id'];
10960
            }
10961
        }
10962
10963
        $lastLpItemIndex = count($lpItemId) - 1;
10964
        $position = array_search($currentItemId, $lpItemId);
10965
10966
        switch ($position) {
10967
            case 0:
10968
                if (!$lastLpItemIndex) {
10969
                    $answer = 'both';
10970
                    break;
10971
                }
10972
10973
                $answer = 'first';
10974
                break;
10975
            case $lastLpItemIndex:
10976
                $answer = 'last';
10977
                break;
10978
            default:
10979
                $answer = 'none';
10980
        }
10981
10982
        return $answer;
10983
    }
10984
10985
    /**
10986
     * Get whether this is a learning path with the accumulated SCORM time or not.
10987
     *
10988
     * @return int
10989
     */
10990
    public function getAccumulateScormTime()
10991
    {
10992
        return $this->accumulateScormTime;
10993
    }
10994
10995
    /**
10996
     * Set whether this is a learning path with the accumulated SCORM time or not.
10997
     *
10998
     * @param int $value (0 = false, 1 = true)
10999
     *
11000
     * @return bool Always returns true
11001
     */
11002
    public function setAccumulateScormTime($value)
11003
    {
11004
        $this->accumulateScormTime = (int) $value;
11005
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
11006
        $lp_id = $this->get_id();
11007
        $sql = "UPDATE $lp_table
11008
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
11009
                WHERE iid = $lp_id";
11010
        Database::query($sql);
11011
11012
        return true;
11013
    }
11014
11015
    /**
11016
     * Returns an HTML-formatted link to a resource, to incorporate directly into
11017
     * the new learning path tool.
11018
     *
11019
     * The function is a big switch on tool type.
11020
     * In each case, we query the corresponding table for information and build the link
11021
     * with that information.
11022
     *
11023
     * @author Yannick Warnier <[email protected]> - rebranding based on
11024
     * previous work (display_addedresource_link_in_learnpath())
11025
     *
11026
     * @param int $course_id      Course code
11027
     * @param int $learningPathId The learning path ID (in lp table)
11028
     * @param int $id_in_path     the unique index in the items table
11029
     * @param int $lpViewId
11030
     *
11031
     * @return string
11032
     */
11033
    public static function rl_get_resource_link_for_learnpath(
11034
        $course_id,
11035
        $learningPathId,
11036
        $id_in_path,
11037
        $lpViewId
11038
    ) {
11039
        $session_id = api_get_session_id();
11040
        $course_info = api_get_course_info_by_id($course_id);
11041
11042
        $learningPathId = (int) $learningPathId;
11043
        $id_in_path = (int) $id_in_path;
11044
        $lpViewId = (int) $lpViewId;
11045
11046
        $em = Database::getManager();
11047
        $lpItemRepo = $em->getRepository(CLpItem::class);
11048
11049
        /** @var CLpItem $rowItem */
11050
        $rowItem = $lpItemRepo->findOneBy([
11051
            'cId' => $course_id,
11052
            'lp' => $learningPathId,
11053
            'iid' => $id_in_path,
11054
        ]);
11055
11056
        if (!$rowItem) {
11057
            // Try one more time with "id"
11058
            /** @var CLpItem $rowItem */
11059
            $rowItem = $lpItemRepo->findOneBy([
11060
                'cId' => $course_id,
11061
                'lp' => $learningPathId,
11062
                'id' => $id_in_path,
11063
            ]);
11064
11065
            if (!$rowItem) {
11066
                return -1;
11067
            }
11068
        }
11069
11070
        $type = $rowItem->getItemType();
11071
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
11072
        $main_dir_path = api_get_path(WEB_CODE_PATH);
11073
        //$main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
11074
        $link = '';
11075
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
11076
11077
        switch ($type) {
11078
            case 'dir':
11079
                return $main_dir_path.'lp/blank.php';
11080
            case TOOL_CALENDAR_EVENT:
11081
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
11082
            case TOOL_ANNOUNCEMENT:
11083
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
11084
            case TOOL_LINK:
11085
                $linkInfo = Link::getLinkInfo($id);
11086
                if (isset($linkInfo['url'])) {
11087
                    return $linkInfo['url'];
11088
                }
11089
11090
                return '';
11091
            case TOOL_QUIZ:
11092
                if (empty($id)) {
11093
                    return '';
11094
                }
11095
11096
                // Get the lp_item_view with the highest view_count.
11097
                $learnpathItemViewResult = $em
11098
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
11099
                    ->findBy(
11100
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
11101
                        ['viewCount' => 'DESC'],
11102
                        1
11103
                    );
11104
                /** @var CLpItemView $learnpathItemViewData */
11105
                $learnpathItemViewData = current($learnpathItemViewResult);
11106
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
11107
11108
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
11109
                    .http_build_query([
11110
                        'lp_init' => 1,
11111
                        'learnpath_item_view_id' => $learnpathItemViewId,
11112
                        'learnpath_id' => $learningPathId,
11113
                        'learnpath_item_id' => $id_in_path,
11114
                        'exerciseId' => $id,
11115
                    ]);
11116
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
11117
                /*$TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
11118
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
11119
                $myrow = Database::fetch_array($result);
11120
                $path = $myrow['path'];
11121
11122
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
11123
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
11124
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;*/
11125
            case TOOL_FORUM:
11126
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
11127
            case TOOL_THREAD:
11128
                // forum post
11129
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
11130
                if (empty($id)) {
11131
                    return '';
11132
                }
11133
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
11134
                $result = Database::query($sql);
11135
                $myrow = Database::fetch_array($result);
11136
11137
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
11138
                    .$extraParams;
11139
            case TOOL_POST:
11140
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
11141
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
11142
                $myrow = Database::fetch_array($result);
11143
11144
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
11145
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
11146
            case TOOL_READOUT_TEXT:
11147
                return api_get_path(WEB_CODE_PATH).
11148
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
11149
            case TOOL_DOCUMENT:
11150
                $repo = Container::getDocumentRepository();
11151
                $document = $repo->find($rowItem->getPath());
11152
                $file = $repo->getResourceFileUrl($document, [], UrlGeneratorInterface::ABSOLUTE_URL);
11153
11154
                return $file;
11155
11156
                $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...
11157
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v'];
11158
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
11159
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
11160
11161
                $openmethod = 2;
11162
                $officedoc = false;
11163
                Session::write('openmethod', $openmethod);
11164
                Session::write('officedoc', $officedoc);
11165
11166
                if ($showDirectUrl) {
11167
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
11168
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
11169
                        if (Link::isPdfLink($file)) {
11170
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
11171
11172
                            return $pdfUrl;
11173
                        }
11174
                    }
11175
11176
                    return $file;
11177
                }
11178
11179
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
11180
            case TOOL_LP_FINAL_ITEM:
11181
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
11182
                    .$extraParams;
11183
            case 'assignments':
11184
                return $main_dir_path.'work/work.php?'.$extraParams;
11185
            case TOOL_DROPBOX:
11186
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
11187
            case 'introduction_text': //DEPRECATED
11188
                return '';
11189
            case TOOL_COURSE_DESCRIPTION:
11190
                return $main_dir_path.'course_description?'.$extraParams;
11191
            case TOOL_GROUP:
11192
                return $main_dir_path.'group/group.php?'.$extraParams;
11193
            case TOOL_USER:
11194
                return $main_dir_path.'user/user.php?'.$extraParams;
11195
            case TOOL_STUDENTPUBLICATION:
11196
                if (!empty($rowItem->getPath())) {
11197
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
11198
                }
11199
11200
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
11201
        }
11202
11203
        return $link;
11204
    }
11205
11206
    /**
11207
     * Gets the name of a resource (generally used in learnpath when no name is provided).
11208
     *
11209
     * @author Yannick Warnier <[email protected]>
11210
     *
11211
     * @param string $course_code    Course code
11212
     * @param int    $learningPathId
11213
     * @param int    $id_in_path     The resource ID
11214
     *
11215
     * @return string
11216
     */
11217
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
11218
    {
11219
        $_course = api_get_course_info($course_code);
11220
        if (empty($_course)) {
11221
            return '';
11222
        }
11223
        $course_id = $_course['real_id'];
11224
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11225
        $learningPathId = (int) $learningPathId;
11226
        $id_in_path = (int) $id_in_path;
11227
11228
        $sql = "SELECT item_type, title, ref
11229
                FROM $tbl_lp_item
11230
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
11231
        $res_item = Database::query($sql);
11232
11233
        if (Database::num_rows($res_item) < 1) {
11234
            return '';
11235
        }
11236
        $row_item = Database::fetch_array($res_item);
11237
        $type = strtolower($row_item['item_type']);
11238
        $id = $row_item['ref'];
11239
        $output = '';
11240
11241
        switch ($type) {
11242
            case TOOL_CALENDAR_EVENT:
11243
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
11244
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
11245
                $myrow = Database::fetch_array($result);
11246
                $output = $myrow['title'];
11247
                break;
11248
            case TOOL_ANNOUNCEMENT:
11249
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
11250
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
11251
                $myrow = Database::fetch_array($result);
11252
                $output = $myrow['title'];
11253
                break;
11254
            case TOOL_LINK:
11255
                // Doesn't take $target into account.
11256
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
11257
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
11258
                $myrow = Database::fetch_array($result);
11259
                $output = $myrow['title'];
11260
                break;
11261
            case TOOL_QUIZ:
11262
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
11263
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
11264
                $myrow = Database::fetch_array($result);
11265
                $output = $myrow['title'];
11266
                break;
11267
            case TOOL_FORUM:
11268
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
11269
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
11270
                $myrow = Database::fetch_array($result);
11271
                $output = $myrow['forum_name'];
11272
                break;
11273
            case TOOL_THREAD:
11274
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
11275
                // Grabbing the title of the post.
11276
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
11277
                $result_title = Database::query($sql_title);
11278
                $myrow_title = Database::fetch_array($result_title);
11279
                $output = $myrow_title['post_title'];
11280
                break;
11281
            case TOOL_POST:
11282
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
11283
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
11284
                $result = Database::query($sql);
11285
                $post = Database::fetch_array($result);
11286
                $output = $post['post_title'];
11287
                break;
11288
            case 'dir':
11289
            case TOOL_DOCUMENT:
11290
                $title = $row_item['title'];
11291
                $output = '-';
11292
                if (!empty($title)) {
11293
                    $output = $title;
11294
                }
11295
                break;
11296
            case 'hotpotatoes':
11297
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
11298
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
11299
                $myrow = Database::fetch_array($result);
11300
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
11301
                $last = count($pathname) - 1; // Making a correct name for the link.
11302
                $filename = $pathname[$last]; // Making a correct name for the link.
11303
                $myrow['path'] = rawurlencode($myrow['path']);
11304
                $output = $filename;
11305
                break;
11306
        }
11307
11308
        return stripslashes($output);
11309
    }
11310
11311
    /**
11312
     * Get the parent names for the current item.
11313
     *
11314
     * @param int $newItemId Optional. The item ID
11315
     *
11316
     * @return array
11317
     */
11318
    public function getCurrentItemParentNames($newItemId = 0)
11319
    {
11320
        $newItemId = $newItemId ?: $this->get_current_item_id();
11321
        $return = [];
11322
        $item = $this->getItem($newItemId);
11323
        $parent = $this->getItem($item->get_parent());
11324
11325
        while ($parent) {
11326
            $return[] = $parent->get_title();
11327
            $parent = $this->getItem($parent->get_parent());
11328
        }
11329
11330
        return array_reverse($return);
11331
    }
11332
11333
    /**
11334
     * Reads and process "lp_subscription_settings" setting.
11335
     *
11336
     * @return array
11337
     */
11338
    public static function getSubscriptionSettings()
11339
    {
11340
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
11341
        if (empty($subscriptionSettings)) {
11342
            // By default allow both settings
11343
            $subscriptionSettings = [
11344
                'allow_add_users_to_lp' => true,
11345
                'allow_add_users_to_lp_category' => true,
11346
            ];
11347
        } else {
11348
            $subscriptionSettings = $subscriptionSettings['options'];
11349
        }
11350
11351
        return $subscriptionSettings;
11352
    }
11353
11354
    /**
11355
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
11356
     */
11357
    public function exportToCourseBuildFormat()
11358
    {
11359
        if (!api_is_allowed_to_edit()) {
11360
            return false;
11361
        }
11362
11363
        $courseBuilder = new CourseBuilder();
11364
        $itemList = [];
11365
        /** @var learnpathItem $item */
11366
        foreach ($this->items as $item) {
11367
            $itemList[$item->get_type()][] = $item->get_path();
11368
        }
11369
11370
        if (empty($itemList)) {
11371
            return false;
11372
        }
11373
11374
        if (isset($itemList['document'])) {
11375
            // Get parents
11376
            foreach ($itemList['document'] as $documentId) {
11377
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
11378
                if (!empty($documentInfo['parents'])) {
11379
                    foreach ($documentInfo['parents'] as $parentInfo) {
11380
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
11381
                            continue;
11382
                        }
11383
                        $itemList['document'][] = $parentInfo['iid'];
11384
                    }
11385
                }
11386
            }
11387
11388
            $courseInfo = api_get_course_info();
11389
            foreach ($itemList['document'] as $documentId) {
11390
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
11391
                $items = DocumentManager::get_resources_from_source_html(
11392
                    $documentInfo['absolute_path'],
11393
                    true,
11394
                    TOOL_DOCUMENT
11395
                );
11396
11397
                if (!empty($items)) {
11398
                    foreach ($items as $item) {
11399
                        // Get information about source url
11400
                        $url = $item[0]; // url
11401
                        $scope = $item[1]; // scope (local, remote)
11402
                        $type = $item[2]; // type (rel, abs, url)
11403
11404
                        $origParseUrl = parse_url($url);
11405
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
11406
11407
                        if ('local' == $scope) {
11408
                            if ('abs' == $type || 'rel' == $type) {
11409
                                $documentFile = strstr($realOrigPath, 'document');
11410
                                if (false !== strpos($realOrigPath, $documentFile)) {
11411
                                    $documentFile = str_replace('document', '', $documentFile);
11412
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
11413
                                    // Document found! Add it to the list
11414
                                    if ($itemDocumentId) {
11415
                                        $itemList['document'][] = $itemDocumentId;
11416
                                    }
11417
                                }
11418
                            }
11419
                        }
11420
                    }
11421
                }
11422
            }
11423
11424
            $courseBuilder->build_documents(
11425
                api_get_session_id(),
11426
                $this->get_course_int_id(),
11427
                true,
11428
                $itemList['document']
11429
            );
11430
        }
11431
11432
        if (isset($itemList['quiz'])) {
11433
            $courseBuilder->build_quizzes(
11434
                api_get_session_id(),
11435
                $this->get_course_int_id(),
11436
                true,
11437
                $itemList['quiz']
11438
            );
11439
        }
11440
11441
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
11442
11443
        /*if (!empty($itemList['thread'])) {
11444
            $postList = [];
11445
            foreach ($itemList['thread'] as $postId) {
11446
                $post = get_post_information($postId);
11447
                if ($post) {
11448
                    if (!isset($itemList['forum'])) {
11449
                        $itemList['forum'] = [];
11450
                    }
11451
                    $itemList['forum'][] = $post['forum_id'];
11452
                    $postList[] = $postId;
11453
                }
11454
            }
11455
11456
            if (!empty($postList)) {
11457
                $courseBuilder->build_forum_posts(
11458
                    $this->get_course_int_id(),
11459
                    null,
11460
                    null,
11461
                    $postList
11462
                );
11463
            }
11464
        }*/
11465
11466
        if (!empty($itemList['thread'])) {
11467
            $threadList = [];
11468
            $em = Database::getManager();
11469
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
11470
            foreach ($itemList['thread'] as $threadId) {
11471
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
11472
                $thread = $repo->find($threadId);
11473
                if ($thread) {
11474
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
11475
                    $threadList[] = $thread->getIid();
11476
                }
11477
            }
11478
11479
            if (!empty($threadList)) {
11480
                $courseBuilder->build_forum_topics(
11481
                    api_get_session_id(),
11482
                    $this->get_course_int_id(),
11483
                    null,
11484
                    $threadList
11485
                );
11486
            }
11487
        }
11488
11489
        $forumCategoryList = [];
11490
        if (isset($itemList['forum'])) {
11491
            foreach ($itemList['forum'] as $forumId) {
11492
                $forumInfo = get_forums($forumId);
11493
                $forumCategoryList[] = $forumInfo['forum_category'];
11494
            }
11495
        }
11496
11497
        if (!empty($forumCategoryList)) {
11498
            $courseBuilder->build_forum_category(
11499
                api_get_session_id(),
11500
                $this->get_course_int_id(),
11501
                true,
11502
                $forumCategoryList
11503
            );
11504
        }
11505
11506
        if (!empty($itemList['forum'])) {
11507
            $courseBuilder->build_forums(
11508
                api_get_session_id(),
11509
                $this->get_course_int_id(),
11510
                true,
11511
                $itemList['forum']
11512
            );
11513
        }
11514
11515
        if (isset($itemList['link'])) {
11516
            $courseBuilder->build_links(
11517
                api_get_session_id(),
11518
                $this->get_course_int_id(),
11519
                true,
11520
                $itemList['link']
11521
            );
11522
        }
11523
11524
        if (!empty($itemList['student_publication'])) {
11525
            $courseBuilder->build_works(
11526
                api_get_session_id(),
11527
                $this->get_course_int_id(),
11528
                true,
11529
                $itemList['student_publication']
11530
            );
11531
        }
11532
11533
        $courseBuilder->build_learnpaths(
11534
            api_get_session_id(),
11535
            $this->get_course_int_id(),
11536
            true,
11537
            [$this->get_id()],
11538
            false
11539
        );
11540
11541
        $courseBuilder->restoreDocumentsFromList();
11542
11543
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
11544
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
11545
        $result = DocumentManager::file_send_for_download(
11546
            $zipPath,
11547
            true,
11548
            $this->get_name().'.zip'
11549
        );
11550
11551
        if ($result) {
11552
            api_not_allowed();
11553
        }
11554
11555
        return true;
11556
    }
11557
11558
    /**
11559
     * Get whether this is a learning path with the accumulated work time or not.
11560
     *
11561
     * @return int
11562
     */
11563
    public function getAccumulateWorkTime()
11564
    {
11565
        return (int) $this->accumulateWorkTime;
11566
    }
11567
11568
    /**
11569
     * Get whether this is a learning path with the accumulated work time or not.
11570
     *
11571
     * @return int
11572
     */
11573
    public function getAccumulateWorkTimeTotalCourse()
11574
    {
11575
        $table = Database::get_course_table(TABLE_LP_MAIN);
11576
        $sql = "SELECT SUM(accumulate_work_time) AS total
11577
                FROM $table
11578
                WHERE c_id = ".$this->course_int_id;
11579
        $result = Database::query($sql);
11580
        $row = Database::fetch_array($result);
11581
11582
        return (int) $row['total'];
11583
    }
11584
11585
    /**
11586
     * Set whether this is a learning path with the accumulated work time or not.
11587
     *
11588
     * @param int $value (0 = false, 1 = true)
11589
     *
11590
     * @return bool
11591
     */
11592
    public function setAccumulateWorkTime($value)
11593
    {
11594
        if (!api_get_configuration_value('lp_minimum_time')) {
11595
            return false;
11596
        }
11597
11598
        $this->accumulateWorkTime = (int) $value;
11599
        $table = Database::get_course_table(TABLE_LP_MAIN);
11600
        $lp_id = $this->get_id();
11601
        $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
11602
                WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
11603
        Database::query($sql);
11604
11605
        return true;
11606
    }
11607
11608
    /**
11609
     * @param int $lpId
11610
     * @param int $courseId
11611
     *
11612
     * @return mixed
11613
     */
11614
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
11615
    {
11616
        $lpId = (int) $lpId;
11617
        $courseId = (int) $courseId;
11618
11619
        $table = Database::get_course_table(TABLE_LP_MAIN);
11620
        $sql = "SELECT accumulate_work_time
11621
                FROM $table
11622
                WHERE c_id = $courseId AND id = $lpId";
11623
        $result = Database::query($sql);
11624
        $row = Database::fetch_array($result);
11625
11626
        return $row['accumulate_work_time'];
11627
    }
11628
11629
    /**
11630
     * @param int $courseId
11631
     *
11632
     * @return int
11633
     */
11634
    public static function getAccumulateWorkTimeTotal($courseId)
11635
    {
11636
        $table = Database::get_course_table(TABLE_LP_MAIN);
11637
        $courseId = (int) $courseId;
11638
        $sql = "SELECT SUM(accumulate_work_time) AS total
11639
                FROM $table
11640
                WHERE c_id = $courseId";
11641
        $result = Database::query($sql);
11642
        $row = Database::fetch_array($result);
11643
11644
        return (int) $row['total'];
11645
    }
11646
11647
    /**
11648
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
11649
     * and put the images in.
11650
     *
11651
     * @return array
11652
     */
11653
    public static function getIconSelect()
11654
    {
11655
        $theme = api_get_visual_theme();
11656
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
11657
        $icons = ['' => get_lang('Please select an option')];
11658
11659
        if (is_dir($path)) {
11660
            $finder = new Finder();
11661
            $finder->files()->in($path);
11662
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
11663
            /** @var SplFileInfo $file */
11664
            foreach ($finder as $file) {
11665
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
11666
                    $icons[$file->getFilename()] = $file->getFilename();
11667
                }
11668
            }
11669
        }
11670
11671
        return $icons;
11672
    }
11673
11674
    /**
11675
     * @param int $lpId
11676
     *
11677
     * @return string
11678
     */
11679
    public static function getSelectedIcon($lpId)
11680
    {
11681
        $extraFieldValue = new ExtraFieldValue('lp');
11682
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
11683
        $icon = '';
11684
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
11685
            $icon = $lpIcon['value'];
11686
        }
11687
11688
        return $icon;
11689
    }
11690
11691
    /**
11692
     * @param int $lpId
11693
     *
11694
     * @return string
11695
     */
11696
    public static function getSelectedIconHtml($lpId)
11697
    {
11698
        $icon = self::getSelectedIcon($lpId);
11699
11700
        if (empty($icon)) {
11701
            return '';
11702
        }
11703
11704
        $theme = api_get_visual_theme();
11705
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
11706
11707
        return Display::img($path);
11708
    }
11709
11710
    /**
11711
     * @param string $value
11712
     *
11713
     * @return string
11714
     */
11715
    public function cleanItemTitle($value)
11716
    {
11717
        $value = Security::remove_XSS(strip_tags($value));
11718
11719
        return $value;
11720
    }
11721
11722
    public function setItemTitle(FormValidator $form)
11723
    {
11724
        if (api_get_configuration_value('save_titles_as_html')) {
11725
            $form->addHtmlEditor(
11726
                'title',
11727
                get_lang('Title'),
11728
                true,
11729
                false,
11730
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
11731
            );
11732
        } else {
11733
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
11734
            $form->applyFilter('title', 'trim');
11735
            $form->applyFilter('title', 'html_filter');
11736
        }
11737
    }
11738
11739
    /**
11740
     * @return array
11741
     */
11742
    public function getItemsForForm($addParentCondition = false)
11743
    {
11744
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11745
        $course_id = api_get_course_int_id();
11746
11747
        $sql = "SELECT * FROM $tbl_lp_item
11748
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
11749
11750
        if ($addParentCondition) {
11751
            $sql .= ' AND parent_item_id = 0 ';
11752
        }
11753
        $sql .= ' ORDER BY display_order ASC';
11754
11755
        $result = Database::query($sql);
11756
        $arrLP = [];
11757
        while ($row = Database::fetch_array($result)) {
11758
            $arrLP[] = [
11759
                'iid' => $row['iid'],
11760
                'id' => $row['iid'],
11761
                'item_type' => $row['item_type'],
11762
                'title' => $this->cleanItemTitle($row['title']),
11763
                'title_raw' => $row['title'],
11764
                'path' => $row['path'],
11765
                'description' => Security::remove_XSS($row['description']),
11766
                'parent_item_id' => $row['parent_item_id'],
11767
                'previous_item_id' => $row['previous_item_id'],
11768
                'next_item_id' => $row['next_item_id'],
11769
                'display_order' => $row['display_order'],
11770
                'max_score' => $row['max_score'],
11771
                'min_score' => $row['min_score'],
11772
                'mastery_score' => $row['mastery_score'],
11773
                'prerequisite' => $row['prerequisite'],
11774
                'max_time_allowed' => $row['max_time_allowed'],
11775
                'prerequisite_min_score' => $row['prerequisite_min_score'],
11776
                'prerequisite_max_score' => $row['prerequisite_max_score'],
11777
            ];
11778
        }
11779
11780
        return $arrLP;
11781
    }
11782
11783
    /**
11784
     * Gets whether this SCORM learning path has been marked to use the score
11785
     * as progress. Takes into account whether the learnpath matches (SCORM
11786
     * content + less than 2 items).
11787
     *
11788
     * @return bool True if the score should be used as progress, false otherwise
11789
     */
11790
    public function getUseScoreAsProgress()
11791
    {
11792
        // If not a SCORM, we don't care about the setting
11793
        if (2 != $this->get_type()) {
11794
            return false;
11795
        }
11796
        // If more than one step in the SCORM, we don't care about the setting
11797
        if ($this->get_total_items_count() > 1) {
11798
            return false;
11799
        }
11800
        $extraFieldValue = new ExtraFieldValue('lp');
11801
        $doUseScore = false;
11802
        $useScore = $extraFieldValue->get_values_by_handler_and_field_variable($this->get_id(), 'use_score_as_progress');
11803
        if (!empty($useScore) && isset($useScore['value'])) {
11804
            $doUseScore = $useScore['value'];
11805
        }
11806
11807
        return $doUseScore;
11808
    }
11809
11810
    /**
11811
     * Get the user identifier (user_id or username
11812
     * Depends on scorm_api_username_as_student_id in app/config/configuration.php.
11813
     *
11814
     * @return string User ID or username, depending on configuration setting
11815
     */
11816
    public static function getUserIdentifierForExternalServices()
11817
    {
11818
        if (api_get_configuration_value('scorm_api_username_as_student_id')) {
11819
            return api_get_user_info(api_get_user_id())['username'];
11820
        } elseif (null != api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id')) {
11821
            $extraFieldValue = new ExtraFieldValue('user');
11822
            $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'));
11823
11824
            return $extrafield['value'];
11825
        } else {
11826
            return api_get_user_id();
11827
        }
11828
    }
11829
11830
    /**
11831
     * Save the new order for learning path items.
11832
     *
11833
     * We have to update parent_item_id, previous_item_id, next_item_id, display_order in the database.
11834
     *
11835
     * @param array $orderList A associative array with item ID as key and parent ID as value.
11836
     * @param int   $courseId
11837
     */
11838
    public static function sortItemByOrderList(array $orderList, $courseId = 0)
11839
    {
11840
        $courseId = $courseId ?: api_get_course_int_id();
11841
        $itemList = new LpItemOrderList();
11842
11843
        foreach ($orderList as $id => $parentId) {
11844
            $item = new LpOrderItem($id, $parentId);
11845
            $itemList->add($item);
11846
        }
11847
11848
        $parents = $itemList->getListOfParents();
11849
11850
        foreach ($parents as $parentId) {
11851
            $sameParentLpItemList = $itemList->getItemWithSameParent($parentId);
11852
            $previous_item_id = 0;
11853
            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...
11854
                $item_id = $sameParentLpItemList->list[$i]->id;
11855
                // display_order
11856
                $display_order = $i + 1;
11857
                $itemList->setParametersForId($item_id, $display_order, 'display_order');
11858
                // previous_item_id
11859
                $itemList->setParametersForId($item_id, $previous_item_id, 'previous_item_id');
11860
                $previous_item_id = $item_id;
11861
                // next_item_id
11862
                $next_item_id = 0;
11863
                if ($i < count($sameParentLpItemList->list) - 1) {
11864
                    $next_item_id = $sameParentLpItemList->list[$i + 1]->id;
11865
                }
11866
                $itemList->setParametersForId($item_id, $next_item_id, 'next_item_id');
11867
            }
11868
        }
11869
11870
        $table = Database::get_course_table(TABLE_LP_ITEM);
11871
11872
        foreach ($itemList->list as $item) {
11873
            $params = [];
11874
            $params['display_order'] = $item->display_order;
11875
            $params['previous_item_id'] = $item->previous_item_id;
11876
            $params['next_item_id'] = $item->next_item_id;
11877
            $params['parent_item_id'] = $item->parent_item_id;
11878
11879
            Database::update(
11880
                $table,
11881
                $params,
11882
                [
11883
                    'iid = ? AND c_id = ? ' => [
11884
                        (int) $item->id,
11885
                        (int) $courseId,
11886
                    ],
11887
                ]
11888
            );
11889
        }
11890
    }
11891
11892
    /**
11893
     * Get the depth level of LP item.
11894
     *
11895
     * @param array $items
11896
     * @param int   $currentItemId
11897
     *
11898
     * @return int
11899
     */
11900
    private static function get_level_for_item($items, $currentItemId)
11901
    {
11902
        $parentItemId = 0;
11903
        if (isset($items[$currentItemId])) {
11904
            $parentItemId = $items[$currentItemId]->parent;
11905
        }
11906
11907
        if (0 == $parentItemId) {
11908
            return 0;
11909
        } else {
11910
            return self::get_level_for_item($items, $parentItemId) + 1;
11911
        }
11912
    }
11913
11914
    /**
11915
     * Generate the link for a learnpath category as course tool.
11916
     *
11917
     * @param int $categoryId
11918
     *
11919
     * @return string
11920
     */
11921
    private static function getCategoryLinkForTool($categoryId)
11922
    {
11923
        $categoryId = (int) $categoryId;
11924
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
11925
            .http_build_query(
11926
                [
11927
                    'action' => 'view_category',
11928
                    'id' => $categoryId,
11929
                ]
11930
            );
11931
11932
        return $link;
11933
    }
11934
11935
    /**
11936
     * Return the scorm item type object with spaces replaced with _
11937
     * The return result is use to build a css classname like scorm_type_$return.
11938
     *
11939
     * @param $in_type
11940
     *
11941
     * @return mixed
11942
     */
11943
    private static function format_scorm_type_item($in_type)
11944
    {
11945
        return str_replace(' ', '_', $in_type);
11946
    }
11947
11948
    /**
11949
     * Check and obtain the lp final item if exist.
11950
     *
11951
     * @return learnpathItem
11952
     */
11953
    private function getFinalItem()
11954
    {
11955
        if (empty($this->items)) {
11956
            return null;
11957
        }
11958
11959
        foreach ($this->items as $item) {
11960
            if ('final_item' !== $item->type) {
11961
                continue;
11962
            }
11963
11964
            return $item;
11965
        }
11966
    }
11967
11968
    /**
11969
     * Get the LP Final Item Template.
11970
     *
11971
     * @return string
11972
     */
11973
    private function getFinalItemTemplate()
11974
    {
11975
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
11976
    }
11977
11978
    /**
11979
     * Get the LP Final Item Url.
11980
     *
11981
     * @return string
11982
     */
11983
    private function getSavedFinalItem()
11984
    {
11985
        $finalItem = $this->getFinalItem();
11986
11987
        $repo = Container::getDocumentRepository();
11988
        /** @var CDocument $document */
11989
        $document = $repo->find($finalItem->path);
11990
11991
        if ($document && $document->getResourceNode()->hasResourceFile()) {
11992
            return  $repo->getResourceFileContent($document);
11993
        }
11994
11995
        return '';
11996
    }
11997
}
11998