Passed
Push — master ( b5492b...d1ef56 )
by Julito
07:53
created

learnpath   F

Complexity

Total Complexity 1396

Size/Duplication

Total Lines 11462
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 5932
c 0
b 0
f 0
dl 0
loc 11462
rs 0.8
wmc 1396

207 Methods

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

How to fix   Complexity   

Complex Class

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

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

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

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